Transaction Hash:
Block:
18217056 at Sep-26-2023 02:44:35 AM +UTC
Transaction Fee:
0.000326618759965875 ETH
$0.78
Gas Used:
39,625 Gas / 8.242744731 Gwei
Emitted Events:
155 |
SolidlyChildProxy.0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925( 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x00000000000000000000000021053356d7e9d20b1cd38642d233afc889a45668, 0x00000000000000000000000071e0e5f31a71062a08aa629afb8587c7a178dfd9, 0000000000000000000000000000000000000000000000000000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x21053356...889a45668 |
0.459867692449687173 Eth
Nonce: 121
|
0.459541073689721298 Eth
Nonce: 122
| 0.000326618759965875 | ||
0x63A65a17...371F28F28 | |||||
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 13.758037212366423595 Eth | 13.758072874866423595 Eth | 0.0000356625 |
Execution Trace
SolidlyChildProxy.095ea7b3( )

SolidlyProxy.STATICCALL( )
-
BaseV2Factory.DELEGATECALL( )
-
-
BaseV2Pair.approve( spender=0x71e0E5F31a71062a08Aa629aFB8587c7A178Dfd9, amount=0 ) => ( True )
File 1 of 4: SolidlyChildProxy
File 2 of 4: SolidlyProxy
File 3 of 4: BaseV2Factory
File 4 of 4: BaseV2Pair
// SPDX-License-Identifier: MIT pragma solidity 0.8.11; interface IFactory { function governanceAddress() external view returns (address); function childSubImplementationAddress() external view returns (address); function childInterfaceAddress() external view returns (address); } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./SolidlyProxy.sol"; import "./interfaces/IFactory.sol"; /** * @notice Child Proxy deployed by factories for pairs, fees, gauges, and bribes. Calls back to the factory to fetch proxy implementation. */ contract SolidlyChildProxy is SolidlyProxy { bytes32 constant FACTORY_SLOT = 0x547b500e425d72fd0723933cceefc203cef652b4736fd04250c3369b3e1a0a72; // keccak256('FACTORY') - 1 modifier onlyFactory() { require(msg.sender == factoryAddress(), "only Factory"); _; } /** * @notice Records factory address and current interface implementation */ constructor() { address _factory = msg.sender; address _interface = IFactory(msg.sender).childInterfaceAddress(); assembly { sstore(FACTORY_SLOT, _factory) sstore(IMPLEMENTATION_SLOT, _interface) // Storing the interface into EIP-1967's implementation slot so Etherscan picks up the interface } } /**************************************** SETTINGS ****************************************/ /** * @notice Governance callable method to update the Factory address */ function updateFactoryAddress(address _factory) external onlyGovernance { assembly { sstore(FACTORY_SLOT, _factory) } } /** * @notice Publically callable function to sync proxy interface with the one recorded in the factory */ function updateInterfaceAddress() external { address _newInterfaceAddress = IFactory(factoryAddress()) .childInterfaceAddress(); require( implementationAddress() != _newInterfaceAddress, "Nothing to update" ); assembly { sstore(IMPLEMENTATION_SLOT, _newInterfaceAddress) } } /**************************************** VIEW METHODS ****************************************/ /** * @notice Fetch current governance address from factory * @return _governanceAddress Returns current governance address */ function governanceAddress() public view override returns (address _governanceAddress) { return IFactory(factoryAddress()).governanceAddress(); } function factoryAddress() public view returns (address _factory) { assembly { _factory := sload(FACTORY_SLOT) } } /** *@notice Fetch address where actual contract logic is at */ function subImplementationAddress() public view returns (address _subimplementation) { return IFactory(factoryAddress()).childSubImplementationAddress(); } /** * @notice Fetch address where the interface for the contract is */ function interfaceAddress() public view override returns (address _interface) { assembly { _interface := sload(IMPLEMENTATION_SLOT) } } /**************************************** FALLBACK METHODS ****************************************/ /** * @notice Fallback function that delegatecalls the subimplementation instead of what's in the IMPLEMENTATION_SLOT */ function _delegateCallSubimplmentation() internal override { address contractLogic = IFactory(factoryAddress()) .childSubImplementationAddress(); assembly { calldatacopy(0x0, 0x0, calldatasize()) let success := delegatecall( gas(), contractLogic, 0x0, calldatasize(), 0, 0 ) let returnDataSize := returndatasize() returndatacopy(0, 0, returnDataSize) switch success case 0 { revert(0, returnDataSize) } default { return(0, returnDataSize) } } } fallback() external payable override { _delegateCallSubimplmentation(); } } // SPDX-License-Identifier: BUSL pragma solidity 0.8.11; /** * @title Solidly+ governance killable proxy * @author Solidly+ * @notice EIP-1967 upgradeable proxy with the ability to kill governance and render the contract immutable */ contract SolidlyProxy { bytes32 constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // keccak256('eip1967.proxy.implementation'), actually used for interface so etherscan picks up the interface bytes32 constant LOGIC_SLOT = 0x5942be825425c77e56e4bce97986794ab0f100954e40fc1390ae0e003710a3ab; // keccak256('LOGIC') - 1, actual logic implementation bytes32 constant GOVERNANCE_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; // keccak256('eip1967.proxy.admin') bytes32 constant INITIALIZED_SLOT = 0x834ce84547018237034401a09067277cdcbe7bbf7d7d30f6b382b0a102b7b4a3; // keccak256('eip1967.proxy.initialized') /** * @notice Reverts if msg.sender is not governance */ modifier onlyGovernance() { require(msg.sender == governanceAddress(), "Only governance"); _; } /** * @notice Reverts if contract is already initialized * @dev Used by implementations to ensure initialize() is only called once */ modifier notInitialized() { bool initialized; assembly { initialized := sload(INITIALIZED_SLOT) if eq(initialized, 1) { revert(0, 0) } sstore(INITIALIZED_SLOT, 1) } _; } /** * @notice Sets up deployer as a proxy governance */ constructor() { address _governanceAddress = msg.sender; assembly { sstore(GOVERNANCE_SLOT, _governanceAddress) } } /** * @notice Detect whether or not governance is killed * @return Return true if governance is killed, false if not * @dev If governance is killed this contract becomes immutable */ function governanceIsKilled() public view returns (bool) { return governanceAddress() == address(0); } /** * @notice Kill governance, making this contract immutable * @dev Only governance can kil governance */ function killGovernance() external onlyGovernance { updateGovernanceAddress(address(0)); } /** * @notice Update implementation address * @param _interfaceAddress Address of the new interface * @dev Only governance can update implementation */ function updateInterfaceAddress(address _interfaceAddress) external onlyGovernance { assembly { sstore(IMPLEMENTATION_SLOT, _interfaceAddress) } } /** * @notice Actually updates interface, kept for etherscan pattern recognition * @param _implementationAddress Address of the new implementation * @dev Only governance can update implementation */ function updateImplementationAddress(address _implementationAddress) external onlyGovernance { assembly { sstore(IMPLEMENTATION_SLOT, _implementationAddress) } } /** * @notice Update implementation address * @param _logicAddress Address of the new implementation * @dev Only governance can update implementation */ function updateLogicAddress(address _logicAddress) external onlyGovernance { assembly { sstore(LOGIC_SLOT, _logicAddress) } } /** * @notice Update governance address * @param _governanceAddress New governance address * @dev Only governance can update governance */ function updateGovernanceAddress(address _governanceAddress) public onlyGovernance { assembly { sstore(GOVERNANCE_SLOT, _governanceAddress) } } /** * @notice Fetch the current implementation address * @return _implementationAddress Returns the current implementation address */ function implementationAddress() public view returns (address _implementationAddress) { assembly { _implementationAddress := sload(IMPLEMENTATION_SLOT) } } /** * @notice Fetch the current implementation address * @return _interfaceAddress Returns the current implementation address */ function interfaceAddress() public view virtual returns (address _interfaceAddress) { assembly { _interfaceAddress := sload(IMPLEMENTATION_SLOT) } } /** * @notice Fetch the current implementation address * @return _logicAddress Returns the current implementation address */ function logicAddress() public view virtual returns (address _logicAddress) { assembly { _logicAddress := sload(LOGIC_SLOT) } } /** * @notice Fetch current governance address * @return _governanceAddress Returns current governance address */ function governanceAddress() public view virtual returns (address _governanceAddress) { assembly { _governanceAddress := sload(GOVERNANCE_SLOT) } } /** * @notice Fallback function that delegatecalls the subimplementation instead of what's in the IMPLEMENTATION_SLOT */ function _delegateCallSubimplmentation() internal virtual { assembly { let contractLogic := sload(LOGIC_SLOT) calldatacopy(0x0, 0x0, calldatasize()) let success := delegatecall( gas(), contractLogic, 0x0, calldatasize(), 0, 0 ) let returnDataSize := returndatasize() returndatacopy(0, 0, returnDataSize) switch success case 0 { revert(0, returnDataSize) } default { return(0, returnDataSize) } } } /** * @notice Delegatecall fallback proxy */ fallback() external payable virtual { _delegateCallSubimplmentation(); } receive() external payable virtual { _delegateCallSubimplmentation(); } }
File 2 of 4: SolidlyProxy
// SPDX-License-Identifier: BUSL pragma solidity 0.8.11; /** * @title Solidly+ governance killable proxy * @author Solidly+ * @notice EIP-1967 upgradeable proxy with the ability to kill governance and render the contract immutable */ contract SolidlyProxy { bytes32 constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // keccak256('eip1967.proxy.implementation'), actually used for interface so etherscan picks up the interface bytes32 constant LOGIC_SLOT = 0x5942be825425c77e56e4bce97986794ab0f100954e40fc1390ae0e003710a3ab; // keccak256('LOGIC') - 1, actual logic implementation bytes32 constant GOVERNANCE_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; // keccak256('eip1967.proxy.admin') bytes32 constant INITIALIZED_SLOT = 0x834ce84547018237034401a09067277cdcbe7bbf7d7d30f6b382b0a102b7b4a3; // keccak256('eip1967.proxy.initialized') /** * @notice Reverts if msg.sender is not governance */ modifier onlyGovernance() { require(msg.sender == governanceAddress(), "Only governance"); _; } /** * @notice Reverts if contract is already initialized * @dev Used by implementations to ensure initialize() is only called once */ modifier notInitialized() { bool initialized; assembly { initialized := sload(INITIALIZED_SLOT) if eq(initialized, 1) { revert(0, 0) } sstore(INITIALIZED_SLOT, 1) } _; } /** * @notice Sets up deployer as a proxy governance */ constructor() { address _governanceAddress = msg.sender; assembly { sstore(GOVERNANCE_SLOT, _governanceAddress) } } /** * @notice Detect whether or not governance is killed * @return Return true if governance is killed, false if not * @dev If governance is killed this contract becomes immutable */ function governanceIsKilled() public view returns (bool) { return governanceAddress() == address(0); } /** * @notice Kill governance, making this contract immutable * @dev Only governance can kil governance */ function killGovernance() external onlyGovernance { updateGovernanceAddress(address(0)); } /** * @notice Update implementation address * @param _interfaceAddress Address of the new interface * @dev Only governance can update implementation */ function updateInterfaceAddress(address _interfaceAddress) external onlyGovernance { assembly { sstore(IMPLEMENTATION_SLOT, _interfaceAddress) } } /** * @notice Actually updates interface, kept for etherscan pattern recognition * @param _implementationAddress Address of the new implementation * @dev Only governance can update implementation */ function updateImplementationAddress(address _implementationAddress) external onlyGovernance { assembly { sstore(IMPLEMENTATION_SLOT, _implementationAddress) } } /** * @notice Update implementation address * @param _logicAddress Address of the new implementation * @dev Only governance can update implementation */ function updateLogicAddress(address _logicAddress) external onlyGovernance { assembly { sstore(LOGIC_SLOT, _logicAddress) } } /** * @notice Update governance address * @param _governanceAddress New governance address * @dev Only governance can update governance */ function updateGovernanceAddress(address _governanceAddress) public onlyGovernance { assembly { sstore(GOVERNANCE_SLOT, _governanceAddress) } } /** * @notice Fetch the current implementation address * @return _implementationAddress Returns the current implementation address */ function implementationAddress() public view returns (address _implementationAddress) { assembly { _implementationAddress := sload(IMPLEMENTATION_SLOT) } } /** * @notice Fetch the current implementation address * @return _interfaceAddress Returns the current implementation address */ function interfaceAddress() public view virtual returns (address _interfaceAddress) { assembly { _interfaceAddress := sload(IMPLEMENTATION_SLOT) } } /** * @notice Fetch the current implementation address * @return _logicAddress Returns the current implementation address */ function logicAddress() public view virtual returns (address _logicAddress) { assembly { _logicAddress := sload(LOGIC_SLOT) } } /** * @notice Fetch current governance address * @return _governanceAddress Returns current governance address */ function governanceAddress() public view virtual returns (address _governanceAddress) { assembly { _governanceAddress := sload(GOVERNANCE_SLOT) } } /** * @notice Fallback function that delegatecalls the subimplementation instead of what's in the IMPLEMENTATION_SLOT */ function _delegateCallSubimplmentation() internal virtual { assembly { let contractLogic := sload(LOGIC_SLOT) calldatacopy(0x0, 0x0, calldatasize()) let success := delegatecall( gas(), contractLogic, 0x0, calldatasize(), 0, 0 ) let returnDataSize := returndatasize() returndatacopy(0, 0, returnDataSize) switch success case 0 { revert(0, returnDataSize) } default { return(0, returnDataSize) } } } /** * @notice Delegatecall fallback proxy */ fallback() external payable virtual { _delegateCallSubimplmentation(); } receive() external payable virtual { _delegateCallSubimplmentation(); } }
File 3 of 4: BaseV2Factory
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./ProxyPattern/SolidlyFactory.sol"; import "./ProxyPattern/SolidlyChildImplementation.sol"; interface erc20 { function totalSupply() external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function decimals() external view returns (uint8); function symbol() external view returns (string memory); function balanceOf(address) external view returns (uint256); function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); function approve(address spender, uint256 value) external returns (bool); } library Math { function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } function sqrt(uint256 y) internal pure returns (uint256 z) { if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } } } interface IBaseV2Callee { function hook( address sender, uint256 amount0, uint256 amount1, bytes calldata data ) external; } interface IBaseV2Voter { function feeDists(address pool) external view returns (address); function generalFees() external view returns (address); } // Base V2 Fees contract is used as a 1:1 pair relationship to split out fees, this ensures that the curve does not need to be modified for LP shares /** * @dev Changelog: * - Deprecate constructor for initialize() * - Immutable storage slots became mutable but made sure nothing changes them after initialize() */ contract BaseV2Fees is SolidlyChildImplementation { address pair; // The pair it is bonded to address token0; // token0 of pair, saved localy and statically for gas optimization address token1; // Token1 of pair, saved localy and statically for gas optimization uint256 lastDistributed0; // last time fee0 was distributed towards bribe uint256 lastDistributed1; // last time fee1 was distributed towards bribe function initialize(address _pair) external onlyFactory notInitialized { pair = _pair; token0 = BaseV2Pair(_pair).token0(); token1 = BaseV2Pair(_pair).token1(); } function _safeTransfer( address token, address to, uint256 value ) internal { require(token.code.length > 0, "!contract"); (bool success, bytes memory data) = token.call( abi.encodeWithSelector(erc20.transfer.selector, to, value) ); require( success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: safeTransfer low-level call failed" ); } // Allow the pair to transfer fees to gauges function claimFeesFor( address recipient, uint256 amount0, uint256 amount1 ) external { require(msg.sender == pair, "Only pair"); if (amount0 > 0) { _safeTransfer(token0, recipient, amount0); lastDistributed0 = block.timestamp; } if (amount1 > 0) { _safeTransfer(token1, recipient, amount1); lastDistributed1 = block.timestamp; } } } // The base pair of pools, either stable or volatile /** * @dev Changelog: * - Deprecate constructor for initialize() * - Immutable storage slots became mutable but made sure nothing changes them after initialize() * - Trading fees go back to the protocol * - Deprecate _updateFor(), index0, index1 because fees go back to the protocol */ contract BaseV2Pair is SolidlyChildImplementation { uint8 public constant decimals = 18; // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 internal constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; uint256 internal constant MINIMUM_LIQUIDITY = 10**3; uint256 internal constant feeDivider = 1e6; /** * @dev storage slots start here */ // simple re-entrancy check uint256 internal _unlocked = 1; string public name; string public symbol; // Used to denote stable or volatile pair, bool public stable; uint256 public feeRatio; uint256 public totalSupply = 0; mapping(address => mapping(address => uint256)) public allowance; mapping(address => uint256) public balanceOf; bytes32 internal DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; address public token0; address public token1; address public fees; address factory; // Structure to capture time period obervations every 30 minutes, used for local oracles struct Observation { uint256 timestamp; uint256 reserve0Cumulative; uint256 reserve1Cumulative; } // Capture oracle reading every 30 minutes uint256 constant periodSize = 1800; Observation[] public observations; uint256 internal decimals0; uint256 internal decimals1; uint256 public reserve0; uint256 public reserve1; uint256 public blockTimestampLast; uint256 public reserve0CumulativeLast; uint256 public reserve1CumulativeLast; event Fees(address indexed sender, uint256 amount0, uint256 amount1); event Mint(address indexed sender, uint256 amount0, uint256 amount1); event Burn( address indexed sender, uint256 amount0, uint256 amount1, address indexed to ); event Swap( address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to ); event Sync(uint256 reserve0, uint256 reserve1); event Claim( address indexed sender, address indexed recipient, uint256 amount0, uint256 amount1 ); event Transfer(address indexed from, address indexed to, uint256 amount); event Approval( address indexed owner, address indexed spender, uint256 amount ); // simple re-entrancy check modifier lock() { require(_unlocked == 1); _unlocked = 2; _; _unlocked = 1; } function initialize( address _token0, address _token1, bool _stable ) external onlyFactory notInitialized { _unlocked = 1; factory = msg.sender; (token0, token1, stable) = (_token0, _token1, _stable); fees = BaseV2Factory(msg.sender).createFees(); if (_stable) { name = string( abi.encodePacked( "StableV2 AMM - ", erc20(_token0).symbol(), "/", erc20(_token1).symbol() ) ); symbol = string( abi.encodePacked( "sAMM-", erc20(_token0).symbol(), "/", erc20(_token1).symbol() ) ); } else { name = string( abi.encodePacked( "VolatileV2 AMM - ", erc20(_token0).symbol(), "/", erc20(_token1).symbol() ) ); symbol = string( abi.encodePacked( "vAMM-", erc20(_token0).symbol(), "/", erc20(_token1).symbol() ) ); } decimals0 = 10**erc20(_token0).decimals(); decimals1 = 10**erc20(_token1).decimals(); observations.push(Observation(block.timestamp, 0, 0)); syncFees(); } function observationLength() external view returns (uint256) { return observations.length; } function lastObservation() public view returns (Observation memory) { return observations[observations.length - 1]; } function metadata() external view returns ( uint256 dec0, uint256 dec1, uint256 r0, uint256 r1, bool st, address t0, address t1, uint256 _feeRatio ) { return ( decimals0, decimals1, reserve0, reserve1, stable, token0, token1, feeRatio ); } function tokens() external view returns (address, address) { return (token0, token1); } /** * @notice directs the fees toward the gauge if it exists, goes to common pool if not */ function claimFees() external returns (uint256 claimed0, uint256 claimed1) { // Determine whether gauge exists IBaseV2Voter voter = IBaseV2Voter(BaseV2Factory(factory).voter()); address feeDistAddress = voter.feeDists(address(this)); bool gaugeExists = feeDistAddress != address(0); if (!gaugeExists) { feeDistAddress = voter.generalFees(); } require( msg.sender == feeDistAddress, "Only feeDist or only general fees if gauge doesn't exist" ); // Sending directly instead of calling notifyRewardAmount(), // relying on the assumption that this method is only callable by feeDists and generalFees // and that those contracts will deal with the accounting properly address _fees = fees; claimed0 = erc20(token0).balanceOf(_fees); claimed1 = erc20(token1).balanceOf(_fees); BaseV2Fees(_fees).claimFeesFor(msg.sender, claimed0, claimed1); emit Claim(msg.sender, msg.sender, claimed0, claimed1); } /** * @notice Accrue fees on token0 * @dev v2 does not record indexes since all fees go back to the protocol */ function _update0(uint256 amount) internal { _safeTransfer(token0, fees, amount); // transfer the fees out to BaseV2Fees emit Fees(msg.sender, amount, 0); } /** * @notice Accrue fees on token1 * @dev v2 does not record indexes since all fees go back to the protocol */ function _update1(uint256 amount) internal { _safeTransfer(token1, fees, amount); // transfer the fees out to BaseV2Fees emit Fees(msg.sender, amount, 0); } function getReserves() public view returns ( uint256 _reserve0, uint256 _reserve1, uint256 _blockTimestampLast ) { _reserve0 = reserve0; _reserve1 = reserve1; _blockTimestampLast = blockTimestampLast; } // update reserves and, on the first call per block, price accumulators function _update( uint256 balance0, uint256 balance1, uint256 _reserve0, uint256 _reserve1 ) internal { uint256 blockTimestamp = block.timestamp; uint256 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { reserve0CumulativeLast += _reserve0 * timeElapsed; reserve1CumulativeLast += _reserve1 * timeElapsed; } Observation memory _point = lastObservation(); timeElapsed = blockTimestamp - _point.timestamp; // compare the last observation with current timestamp, if greater than 30 minutes, record a new event if (timeElapsed > periodSize) { observations.push( Observation( blockTimestamp, reserve0CumulativeLast, reserve1CumulativeLast ) ); } reserve0 = balance0; reserve1 = balance1; blockTimestampLast = blockTimestamp; emit Sync(reserve0, reserve1); } // produces the cumulative price using counterfactuals to save gas and avoid a call to sync. function currentCumulativePrices() public view returns ( uint256 reserve0Cumulative, uint256 reserve1Cumulative, uint256 blockTimestamp ) { blockTimestamp = block.timestamp; reserve0Cumulative = reserve0CumulativeLast; reserve1Cumulative = reserve1CumulativeLast; // if time has elapsed since the last update on the pair, mock the accumulated price values ( uint256 _reserve0, uint256 _reserve1, uint256 _blockTimestampLast ) = getReserves(); if (_blockTimestampLast != blockTimestamp) { // subtraction overflow is desired uint256 timeElapsed = blockTimestamp - _blockTimestampLast; reserve0Cumulative += _reserve0 * timeElapsed; reserve1Cumulative += _reserve1 * timeElapsed; } } // gives the current twap price measured from amountIn * tokenIn gives amountOut function current(address tokenIn, uint256 amountIn) external view returns (uint256 amountOut) { Observation memory _observation = lastObservation(); ( uint256 reserve0Cumulative, uint256 reserve1Cumulative, ) = currentCumulativePrices(); if (block.timestamp == _observation.timestamp) { _observation = observations[observations.length - 2]; } uint256 timeElapsed = block.timestamp - _observation.timestamp; uint256 _reserve0 = (reserve0Cumulative - _observation.reserve0Cumulative) / timeElapsed; uint256 _reserve1 = (reserve1Cumulative - _observation.reserve1Cumulative) / timeElapsed; amountOut = _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1); } // as per `current`, however allows user configured granularity, up to the full window size function quote( address tokenIn, uint256 amountIn, uint256 granularity ) external view returns (uint256 amountOut) { uint256[] memory _prices = sample(tokenIn, amountIn, granularity, 1); uint256 priceAverageCumulative; for (uint256 i = 0; i < _prices.length; i++) { priceAverageCumulative += _prices[i]; } return priceAverageCumulative / granularity; } // returns a memory set of twap prices function prices( address tokenIn, uint256 amountIn, uint256 points ) external view returns (uint256[] memory) { return sample(tokenIn, amountIn, points, 1); } function sample( address tokenIn, uint256 amountIn, uint256 points, uint256 window ) public view returns (uint256[] memory) { uint256[] memory _prices = new uint256[](points); uint256 length = observations.length - 1; uint256 i = length - (points * window); uint256 nextIndex = 0; uint256 index = 0; for (; i < length; i += window) { nextIndex = i + window; uint256 timeElapsed = observations[nextIndex].timestamp - observations[i].timestamp; uint256 _reserve0 = (observations[nextIndex].reserve0Cumulative - observations[i].reserve0Cumulative) / timeElapsed; uint256 _reserve1 = (observations[nextIndex].reserve1Cumulative - observations[i].reserve1Cumulative) / timeElapsed; _prices[index] = _getAmountOut( amountIn, tokenIn, _reserve0, _reserve1 ); index = index + 1; } return _prices; } // this low-level function should be called from a contract which performs important safety checks // standard uniswap v2 implementation function mint(address to) external lock returns (uint256 liquidity) { (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); uint256 _balance0 = erc20(token0).balanceOf(address(this)); uint256 _balance1 = erc20(token1).balanceOf(address(this)); uint256 _amount0 = _balance0 - _reserve0; uint256 _amount1 = _balance1 - _reserve1; uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee if (_totalSupply == 0) { liquidity = Math.sqrt(_amount0 * _amount1) - MINIMUM_LIQUIDITY; _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens } else { liquidity = Math.min( (_amount0 * _totalSupply) / _reserve0, (_amount1 * _totalSupply) / _reserve1 ); } require(liquidity > 0, "ILM"); // BaseV2: INSUFFICIENT_LIQUIDITY_MINTED _mint(to, liquidity); _update(_balance0, _balance1, _reserve0, _reserve1); emit Mint(msg.sender, _amount0, _amount1); } // this low-level function should be called from a contract which performs important safety checks // standard uniswap v2 implementation function burn(address to) external lock returns (uint256 amount0, uint256 amount1) { (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); (address _token0, address _token1) = (token0, token1); uint256 _balance0 = erc20(_token0).balanceOf(address(this)); uint256 _balance1 = erc20(_token1).balanceOf(address(this)); uint256 _liquidity = balanceOf[address(this)]; uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee amount0 = (_liquidity * _balance0) / _totalSupply; // using balances ensures pro-rata distribution amount1 = (_liquidity * _balance1) / _totalSupply; // using balances ensures pro-rata distribution require(amount0 > 0 && amount1 > 0, "ILB"); // BaseV2: INSUFFICIENT_LIQUIDITY_BURNED _burn(address(this), _liquidity); _safeTransfer(_token0, to, amount0); _safeTransfer(_token1, to, amount1); _balance0 = erc20(_token0).balanceOf(address(this)); _balance1 = erc20(_token1).balanceOf(address(this)); _update(_balance0, _balance1, _reserve0, _reserve1); emit Burn(msg.sender, amount0, amount1, to); } // this low-level function should be called from a contract which performs important safety checks function swap( uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data ) external lock { require(!BaseV2Factory(factory).isPaused(), "Paused"); require(amount0Out > 0 || amount1Out > 0, "IOA"); // BaseV2: INSUFFICIENT_OUTPUT_AMOUNT (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); require(amount0Out < _reserve0 && amount1Out < _reserve1, "IL"); // BaseV2: INSUFFICIENT_LIQUIDITY uint256 _balance0; uint256 _balance1; { // scope for _token{0,1}, avoids stack too deep errors (address _token0, address _token1) = (token0, token1); require(to != _token0 && to != _token1, "IT"); // BaseV2: INVALID_TO if (amount0Out > 0) { _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens } if (amount1Out > 0) { _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens } if (data.length > 0) { IBaseV2Callee(to).hook( msg.sender, amount0Out, amount1Out, data ); // callback, used for flash loans } _balance0 = erc20(_token0).balanceOf(address(this)); _balance1 = erc20(_token1).balanceOf(address(this)); } uint256 amount0In = _balance0 > _reserve0 - amount0Out ? _balance0 - (_reserve0 - amount0Out) : 0; uint256 amount1In = _balance1 > _reserve1 - amount1Out ? _balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, "IIA"); // BaseV2: INSUFFICIENT_INPUT_AMOUNT { // scope for reserve{0,1}Adjusted, avoids stack too deep errors (address _token0, address _token1) = (token0, token1); /** * @dev uses gasleft() as a pseudo-random number. Deterministic behaviour here is actually * good, since it means gas usage won't flucuate with time or blocknumber/hash */ // if (gasleft() % 250 == 0) { // syncFees(); // } if (amount0In > 0) { _update0((amount0In * feeRatio) / feeDivider); // accrue fees for token0 and move them out of pool } if (amount1In > 0) { _update1((amount1In * feeRatio) / feeDivider); // accrue fees for token1 and move them out of pool } _balance0 = erc20(_token0).balanceOf(address(this)); // since we removed tokens, we need to reconfirm balances, can also simply use previous balance - fee, but doing balanceOf again as safety check _balance1 = erc20(_token1).balanceOf(address(this)); // The curve, either x3y+y3x for stable pools, or x*y for volatile pools require(_k(_balance0, _balance1) >= _k(_reserve0, _reserve1), "K"); // BaseV2: K } _update(_balance0, _balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } /** * @notice Syncs fees from pair factory */ function syncFees() public { feeRatio = BaseV2Factory(factory).poolFees(address(this)); } // force balances to match reserves function skim(address to) external lock { (address _token0, address _token1) = (token0, token1); _safeTransfer( _token0, to, erc20(_token0).balanceOf(address(this)) - (reserve0) ); _safeTransfer( _token1, to, erc20(_token1).balanceOf(address(this)) - (reserve1) ); } // force reserves to match balances function sync() external lock { _update( erc20(token0).balanceOf(address(this)), erc20(token1).balanceOf(address(this)), reserve0, reserve1 ); } function _f(uint256 x0, uint256 y) internal pure returns (uint256) { return (x0 * ((((y * y) / 1e18) * y) / 1e18)) / 1e18 + (((((x0 * x0) / 1e18) * x0) / 1e18) * y) / 1e18; } function _d(uint256 x0, uint256 y) internal pure returns (uint256) { return (3 * x0 * ((y * y) / 1e18)) / 1e18 + ((((x0 * x0) / 1e18) * x0) / 1e18); } function _get_y( uint256 x0, uint256 xy, uint256 y ) internal pure returns (uint256) { for (uint256 i = 0; i < 255; i++) { uint256 y_prev = y; uint256 k = _f(x0, y); if (k < xy) { uint256 dy = ((xy - k) * 1e18) / _d(x0, y); y = y + dy; } else { uint256 dy = ((k - xy) * 1e18) / _d(x0, y); y = y - dy; } if (y > y_prev) { if (y - y_prev <= 1) { return y; } } else { if (y_prev - y <= 1) { return y; } } } return y; } function getAmountOut(uint256 amountIn, address tokenIn) external view returns (uint256) { (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); amountIn -= (amountIn * feeRatio) / feeDivider; // remove fee from amount received return _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1); } function _getAmountOut( uint256 amountIn, address tokenIn, uint256 _reserve0, uint256 _reserve1 ) internal view returns (uint256) { if (stable) { uint256 xy = _k(_reserve0, _reserve1); _reserve0 = (_reserve0 * 1e18) / decimals0; _reserve1 = (_reserve1 * 1e18) / decimals1; (uint256 reserveA, uint256 reserveB) = tokenIn == token0 ? (_reserve0, _reserve1) : (_reserve1, _reserve0); amountIn = tokenIn == token0 ? (amountIn * 1e18) / decimals0 : (amountIn * 1e18) / decimals1; uint256 y = reserveB - _get_y(amountIn + reserveA, xy, reserveB); return (y * (tokenIn == token0 ? decimals1 : decimals0)) / 1e18; } else { (uint256 reserveA, uint256 reserveB) = tokenIn == token0 ? (_reserve0, _reserve1) : (_reserve1, _reserve0); return (amountIn * reserveB) / (reserveA + amountIn); } } function _k(uint256 x, uint256 y) internal view returns (uint256) { if (stable) { uint256 _x = (x * 1e18) / decimals0; uint256 _y = (y * 1e18) / decimals1; uint256 _a = (_x * _y) / 1e18; uint256 _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18); return (_a * _b) / 1e18; // x3y+y3x >= k } else { return x * y; // xy >= k } } function _mint(address dst, uint256 amount) internal { totalSupply += amount; balanceOf[dst] += amount; emit Transfer(address(0), dst, amount); } function _burn(address dst, uint256 amount) internal { totalSupply -= amount; balanceOf[dst] -= amount; emit Transfer(dst, address(0), amount); } function approve(address spender, uint256 amount) external returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { require(deadline >= block.timestamp, "BaseV2: EXPIRED"); DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); bytes32 digest = keccak256( abi.encodePacked( "\\x19\\x01", DOMAIN_SEPARATOR, keccak256( abi.encode( PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline ) ) ) ); address recoveredAddress = ecrecover(digest, v, r, s); require( recoveredAddress != address(0) && recoveredAddress == owner, "BaseV2: INVALID_SIGNATURE" ); allowance[owner][spender] = value; emit Approval(owner, spender, value); } function transfer(address dst, uint256 amount) external returns (bool) { _transferTokens(msg.sender, dst, amount); return true; } function transferFrom( address src, address dst, uint256 amount ) external returns (bool) { address spender = msg.sender; uint256 spenderAllowance = allowance[src][spender]; if (spender != src && spenderAllowance != type(uint256).max) { uint256 newAllowance = spenderAllowance - amount; allowance[src][spender] = newAllowance; emit Approval(src, spender, newAllowance); } _transferTokens(src, dst, amount); return true; } function _transferTokens( address src, address dst, uint256 amount ) internal { balanceOf[src] -= amount; balanceOf[dst] += amount; emit Transfer(src, dst, amount); } function _safeTransfer( address token, address to, uint256 value ) internal { require(token.code.length > 0, "!contract"); (bool success, bytes memory data) = token.call( abi.encodeWithSelector(erc20.transfer.selector, to, value) ); require( success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: safeTransfer low-level call failed" ); } } /** * @dev Changelog: * - Deprecate constructor with initialize() * - Deprecate pauser role with onlyGovernance * - Deprecate _temp, _temp0, _temp1, and getInitializable() * - Split out feesFactory for subimplementation proxy pattern * - Added records for feesFactory and voter * - Added stable, volatile fees and setter methods */ contract BaseV2Factory is SolidlyFactory { bool public isPaused; address public feesFactory; address public voter; mapping(address => mapping(address => mapping(bool => address))) public getPair; address[] public allPairs; mapping(address => bool) public isPair; // simplified check if its a pair, given that `stable` flag might not be available in peripherals uint256 public maxFees; // 1_000_000 = 100% uint256 public stableFees; uint256 public volatileFees; mapping(address => bool) public poolSpecificFeesEnabled; mapping(address => uint256) public poolSpecificFees; mapping(address => bool) public isOperator; /**************************************** Events ****************************************/ event OperatorStatus(address indexed operator, bool state); event PairCreated( address indexed token0, address indexed token1, bool stable, address pair, uint256 ); /**************************************** Modifiers ****************************************/ modifier onlyGovernanceOrOperator() { require(isOperator[msg.sender] || msg.sender == governanceAddress()); _; } /**************************************** Initialize ****************************************/ function initialize(address _feesFactory, address _voter) external onlyGovernance notInitialized { feesFactory = _feesFactory; voter = _voter; stableFees = 200; // 0.02% volatileFees = 2000; // 0.20% maxFees = 30000; // 3% } /**************************************** Restricted Methods ****************************************/ /** * @notice Sets operator status * @dev Operators are allowed to pause and set pool fees */ function setOperator(address operator, bool state) external onlyGovernance { if (isOperator[operator] != state) { isOperator[operator] = state; emit OperatorStatus(operator, state); } } function setPause(bool _state) external onlyGovernanceOrOperator { isPaused = _state; } function setMaxFees(uint256 _maxFees) external onlyGovernance { require(_maxFees <= 1e6, "Over 100%"); maxFees = _maxFees; } function setStableFees(uint256 _stableFees) external onlyGovernanceOrOperator { require(_stableFees < maxFees, "Over max fees"); stableFees = _stableFees; } function setVolatileFees(uint256 _volatileFees) external onlyGovernanceOrOperator { require(_volatileFees < maxFees, "Over max fees"); volatileFees = _volatileFees; } /** * @notice Sets specific pool's fees * @dev _enabled needs to be set to true, to differentiate between * pools with 0% fees and pools without specific fees */ function setPoolSpecificFees( address _pool, uint256 _fees, bool _enabled ) external onlyGovernanceOrOperator { require(_fees < maxFees, "Over max fees"); poolSpecificFeesEnabled[_pool] = _enabled; poolSpecificFees[_pool] = _fees; // Sync pool's fees BaseV2Pair(_pool).syncFees(); } /**************************************** View Methods ****************************************/ function allPairsLength() external view returns (uint256) { return allPairs.length; } function pairCodeHash() external pure returns (bytes32) { return keccak256(type(SolidlyChildProxy).creationCode); } /** * @notice Returns fee in basis points for a pool */ function poolFees(address pool) external view returns (uint256) { // Return pool specific fees if enabled if (poolSpecificFeesEnabled[pool]) { return poolSpecificFees[pool]; } // Return volatile fees if not stable if (!BaseV2Pair(pool).stable()) { return volatileFees; } // Return stable fees otherwise return stableFees; } /**************************************** User Interaction ****************************************/ function createPair( address tokenA, address tokenB, bool stable ) external returns (address pair) { require(tokenA != tokenB, "IA"); // BaseV2: IDENTICAL_ADDRESSES (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0), "ZA"); // BaseV2: ZERO_ADDRESS require(getPair[token0][token1][stable] == address(0), "PE"); // BaseV2: PAIR_EXISTS - single check is sufficient bytes32 salt = keccak256(abi.encodePacked(token0, token1, stable)); // notice salt includes stable as well, 3 parameters pair = _deployChildProxyWithSalt(salt); BaseV2Pair(pair).initialize(token0, token1, stable); getPair[token0][token1][stable] = pair; getPair[token1][token0][stable] = pair; // populate mapping in the reverse direction allPairs.push(pair); isPair[pair] = true; emit PairCreated(token0, token1, stable, pair, allPairs.length); } function createFees() external returns (address fees) { fees = BaseV2FeesFactory(feesFactory).createFees(msg.sender); } } /** * @dev Introduced in v2 so each factory only need to carry one set of interface and subimplementation */ contract BaseV2FeesFactory is SolidlyFactory { function createFees(address _pair) external returns (address fees) { fees = _deployChildProxy(); BaseV2Fees(fees).initialize(_pair); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.11; interface IFactory { function governanceAddress() external view returns (address); function childSubImplementationAddress() external view returns (address); function childInterfaceAddress() external view returns (address); } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./SolidlyImplementation.sol"; import "./interfaces/IFactory.sol"; contract SolidlyChildImplementation is SolidlyImplementation { bytes32 constant FACTORY_SLOT = 0x547b500e425d72fd0723933cceefc203cef652b4736fd04250c3369b3e1a0a72; // keccak256('FACTORY') - 1 modifier onlyFactory() { require(msg.sender == factoryAddress(), "only Factory"); _; } /**************************************** VIEW METHODS ****************************************/ /** * @notice Fetch current governance address from factory * @return _governanceAddress Returns current governance address */ function governanceAddress() public view override returns (address _governanceAddress) { return IFactory(factoryAddress()).governanceAddress(); } function factoryAddress() public view returns (address _factory) { assembly { _factory := sload(FACTORY_SLOT) } } } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./SolidlyProxy.sol"; import "./interfaces/IFactory.sol"; /** * @notice Child Proxy deployed by factories for pairs, fees, gauges, and bribes. Calls back to the factory to fetch proxy implementation. */ contract SolidlyChildProxy is SolidlyProxy { bytes32 constant FACTORY_SLOT = 0x547b500e425d72fd0723933cceefc203cef652b4736fd04250c3369b3e1a0a72; // keccak256('FACTORY') - 1 modifier onlyFactory() { require(msg.sender == factoryAddress(), "only Factory"); _; } /** * @notice Records factory address and current interface implementation */ constructor() { address _factory = msg.sender; address _interface = IFactory(msg.sender).childInterfaceAddress(); assembly { sstore(FACTORY_SLOT, _factory) sstore(IMPLEMENTATION_SLOT, _interface) // Storing the interface into EIP-1967's implementation slot so Etherscan picks up the interface } } /**************************************** SETTINGS ****************************************/ /** * @notice Governance callable method to update the Factory address */ function updateFactoryAddress(address _factory) external onlyGovernance { assembly { sstore(FACTORY_SLOT, _factory) } } /** * @notice Publically callable function to sync proxy interface with the one recorded in the factory */ function updateInterfaceAddress() external { address _newInterfaceAddress = IFactory(factoryAddress()) .childInterfaceAddress(); require( implementationAddress() != _newInterfaceAddress, "Nothing to update" ); assembly { sstore(IMPLEMENTATION_SLOT, _newInterfaceAddress) } } /**************************************** VIEW METHODS ****************************************/ /** * @notice Fetch current governance address from factory * @return _governanceAddress Returns current governance address */ function governanceAddress() public view override returns (address _governanceAddress) { return IFactory(factoryAddress()).governanceAddress(); } function factoryAddress() public view returns (address _factory) { assembly { _factory := sload(FACTORY_SLOT) } } /** *@notice Fetch address where actual contract logic is at */ function subImplementationAddress() public view returns (address _subimplementation) { return IFactory(factoryAddress()).childSubImplementationAddress(); } /** * @notice Fetch address where the interface for the contract is */ function interfaceAddress() public view override returns (address _interface) { assembly { _interface := sload(IMPLEMENTATION_SLOT) } } /**************************************** FALLBACK METHODS ****************************************/ /** * @notice Fallback function that delegatecalls the subimplementation instead of what's in the IMPLEMENTATION_SLOT */ function _delegateCallSubimplmentation() internal override { address contractLogic = IFactory(factoryAddress()) .childSubImplementationAddress(); assembly { calldatacopy(0x0, 0x0, calldatasize()) let success := delegatecall( gas(), contractLogic, 0x0, calldatasize(), 0, 0 ) let returnDataSize := returndatasize() returndatacopy(0, 0, returnDataSize) switch success case 0 { revert(0, returnDataSize) } default { return(0, returnDataSize) } } } fallback() external payable override { _delegateCallSubimplmentation(); } receive() external payable override { _delegateCallSubimplmentation(); } } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./SolidlyImplementation.sol"; import "./SolidlyChildProxy.sol"; contract SolidlyFactory is SolidlyImplementation { bytes32 constant CHILD_SUBIMPLEMENTATION_SLOT = 0xa7461aa7cde97eb2572f8234e341359c6baae47e1feeb3c235edffe5f0fc089d; // keccak256('CHILD_SUBIMPLEMENTATION') - 1 bytes32 constant CHILD_INTERFACE_SLOT = 0x23762bb6469fe7a7bd6609262f442817ed09ca1f07add24ef069610d59c90649; // keccak256('CHILD_INTERFACE') - 1 bytes32 constant SUBIMPLEMENTATION_SLOT = 0xa1056f3ed783ff191ada02861fcb19d9ae3a8f50b739813a127951ef5290458d; // keccak256('SUBIMPLEMENTATION') - 1 bytes32 constant INTERFACE_SLOT = 0x4a9bf2931aa5eae439c602abae4bd662e7919244decac463e2e35fc862c5fb98; // keccak256('INTERFACE') - 1 address public interfaceSourceAddress; function _deployChildProxy() internal returns (address) { address addr = address(new SolidlyChildProxy()); return addr; } function _deployChildProxyWithSalt(bytes32 salt) internal returns (address) { address addr = address(new SolidlyChildProxy{salt: salt}()); return addr; } function updateChildSubImplementationAddress( address _childSubImplementationAddress ) external onlyGovernance { assembly { sstore(CHILD_SUBIMPLEMENTATION_SLOT, _childSubImplementationAddress) } } function updateChildInterfaceAddress(address _childInterfaceAddress) external onlyGovernance { assembly { sstore(CHILD_INTERFACE_SLOT, _childInterfaceAddress) } } function childSubImplementationAddress() external view returns (address _childSubImplementation) { assembly { _childSubImplementation := sload(CHILD_SUBIMPLEMENTATION_SLOT) } } function childInterfaceAddress() external view returns (address _childInterface) { assembly { _childInterface := sload(CHILD_INTERFACE_SLOT) } } } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; /** * @title Solidly+ Implementation * @author Solidly+ * @notice Governable implementation that relies on governance slot to be set by the proxy */ contract SolidlyImplementation { bytes32 constant GOVERNANCE_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; // keccak256('eip1967.proxy.admin') bytes32 constant INITIALIZED_SLOT = 0x834ce84547018237034401a09067277cdcbe7bbf7d7d30f6b382b0a102b7b4a3; // keccak256('eip1967.proxy.initialized') /** * @notice Reverts if msg.sender is not governance */ modifier onlyGovernance() { require(msg.sender == governanceAddress(), "Only governance"); _; } /** * @notice Reverts if contract is already initialized * @dev U4sed by implementations to ensure initialize() is only called once */ modifier notInitialized() { bool initialized; assembly { initialized := sload(INITIALIZED_SLOT) if eq(initialized, 1) { revert(0, 0) } sstore(INITIALIZED_SLOT, 1) } _; } /** * @notice Fetch current governance address * @return _governanceAddress Returns current governance address */ function governanceAddress() public view virtual returns (address _governanceAddress) { assembly { _governanceAddress := sload(GOVERNANCE_SLOT) } } } // SPDX-License-Identifier: BUSL pragma solidity 0.8.11; /** * @title Solidly+ governance killable proxy * @author Solidly+ * @notice EIP-1967 upgradeable proxy with the ability to kill governance and render the contract immutable */ contract SolidlyProxy { bytes32 constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // keccak256('eip1967.proxy.implementation'), actually used for interface so etherscan picks up the interface bytes32 constant LOGIC_SLOT = 0x5942be825425c77e56e4bce97986794ab0f100954e40fc1390ae0e003710a3ab; // keccak256('LOGIC') - 1, actual logic implementation bytes32 constant GOVERNANCE_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; // keccak256('eip1967.proxy.admin') bytes32 constant INITIALIZED_SLOT = 0x834ce84547018237034401a09067277cdcbe7bbf7d7d30f6b382b0a102b7b4a3; // keccak256('eip1967.proxy.initialized') /** * @notice Reverts if msg.sender is not governance */ modifier onlyGovernance() { require(msg.sender == governanceAddress(), "Only governance"); _; } /** * @notice Reverts if contract is already initialized * @dev Used by implementations to ensure initialize() is only called once */ modifier notInitialized() { bool initialized; assembly { initialized := sload(INITIALIZED_SLOT) if eq(initialized, 1) { revert(0, 0) } sstore(INITIALIZED_SLOT, 1) } _; } /** * @notice Sets up deployer as a proxy governance */ constructor() { address _governanceAddress = msg.sender; assembly { sstore(GOVERNANCE_SLOT, _governanceAddress) } } /** * @notice Detect whether or not governance is killed * @return Return true if governance is killed, false if not * @dev If governance is killed this contract becomes immutable */ function governanceIsKilled() public view returns (bool) { return governanceAddress() == address(0); } /** * @notice Kill governance, making this contract immutable * @dev Only governance can kil governance */ function killGovernance() external onlyGovernance { updateGovernanceAddress(address(0)); } /** * @notice Update implementation address * @param _interfaceAddress Address of the new interface * @dev Only governance can update implementation */ function updateInterfaceAddress(address _interfaceAddress) external onlyGovernance { assembly { sstore(IMPLEMENTATION_SLOT, _interfaceAddress) } } /** * @notice Actually updates interface, kept for etherscan pattern recognition * @param _implementationAddress Address of the new implementation * @dev Only governance can update implementation */ function updateImplementationAddress(address _implementationAddress) external onlyGovernance { assembly { sstore(IMPLEMENTATION_SLOT, _implementationAddress) } } /** * @notice Update implementation address * @param _logicAddress Address of the new implementation * @dev Only governance can update implementation */ function updateLogicAddress(address _logicAddress) external onlyGovernance { assembly { sstore(LOGIC_SLOT, _logicAddress) } } /** * @notice Update governance address * @param _governanceAddress New governance address * @dev Only governance can update governance */ function updateGovernanceAddress(address _governanceAddress) public onlyGovernance { assembly { sstore(GOVERNANCE_SLOT, _governanceAddress) } } /** * @notice Fetch the current implementation address * @return _implementationAddress Returns the current implementation address */ function implementationAddress() public view returns (address _implementationAddress) { assembly { _implementationAddress := sload(IMPLEMENTATION_SLOT) } } /** * @notice Fetch the current implementation address * @return _interfaceAddress Returns the current implementation address */ function interfaceAddress() public view virtual returns (address _interfaceAddress) { assembly { _interfaceAddress := sload(IMPLEMENTATION_SLOT) } } /** * @notice Fetch the current implementation address * @return _logicAddress Returns the current implementation address */ function logicAddress() public view virtual returns (address _logicAddress) { assembly { _logicAddress := sload(LOGIC_SLOT) } } /** * @notice Fetch current governance address * @return _governanceAddress Returns current governance address */ function governanceAddress() public view virtual returns (address _governanceAddress) { assembly { _governanceAddress := sload(GOVERNANCE_SLOT) } } /** * @notice Fallback function that delegatecalls the subimplementation instead of what's in the IMPLEMENTATION_SLOT */ function _delegateCallSubimplmentation() internal virtual { assembly { let contractLogic := sload(LOGIC_SLOT) calldatacopy(0x0, 0x0, calldatasize()) let success := delegatecall( gas(), contractLogic, 0x0, calldatasize(), 0, 0 ) let returnDataSize := returndatasize() returndatacopy(0, 0, returnDataSize) switch success case 0 { revert(0, returnDataSize) } default { return(0, returnDataSize) } } } /** * @notice Delegatecall fallback proxy */ fallback() external payable virtual { _delegateCallSubimplmentation(); } receive() external payable virtual { _delegateCallSubimplmentation(); } }
File 4 of 4: BaseV2Pair
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./ProxyPattern/SolidlyFactory.sol"; import "./ProxyPattern/SolidlyChildImplementation.sol"; interface erc20 { function totalSupply() external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function decimals() external view returns (uint8); function symbol() external view returns (string memory); function balanceOf(address) external view returns (uint256); function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); function approve(address spender, uint256 value) external returns (bool); } library Math { function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } function sqrt(uint256 y) internal pure returns (uint256 z) { if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } } } interface IBaseV2Callee { function hook( address sender, uint256 amount0, uint256 amount1, bytes calldata data ) external; } interface IBaseV2Voter { function feeDists(address pool) external view returns (address); function generalFees() external view returns (address); } // Base V2 Fees contract is used as a 1:1 pair relationship to split out fees, this ensures that the curve does not need to be modified for LP shares /** * @dev Changelog: * - Deprecate constructor for initialize() * - Immutable storage slots became mutable but made sure nothing changes them after initialize() */ contract BaseV2Fees is SolidlyChildImplementation { address pair; // The pair it is bonded to address token0; // token0 of pair, saved localy and statically for gas optimization address token1; // Token1 of pair, saved localy and statically for gas optimization uint256 lastDistributed0; // last time fee0 was distributed towards bribe uint256 lastDistributed1; // last time fee1 was distributed towards bribe function initialize(address _pair) external onlyFactory notInitialized { pair = _pair; token0 = BaseV2Pair(_pair).token0(); token1 = BaseV2Pair(_pair).token1(); } function _safeTransfer( address token, address to, uint256 value ) internal { require(token.code.length > 0, "!contract"); (bool success, bytes memory data) = token.call( abi.encodeWithSelector(erc20.transfer.selector, to, value) ); require( success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: safeTransfer low-level call failed" ); } // Allow the pair to transfer fees to gauges function claimFeesFor( address recipient, uint256 amount0, uint256 amount1 ) external { require(msg.sender == pair, "Only pair"); if (amount0 > 0) { _safeTransfer(token0, recipient, amount0); lastDistributed0 = block.timestamp; } if (amount1 > 0) { _safeTransfer(token1, recipient, amount1); lastDistributed1 = block.timestamp; } } } // The base pair of pools, either stable or volatile /** * @dev Changelog: * - Deprecate constructor for initialize() * - Immutable storage slots became mutable but made sure nothing changes them after initialize() * - Trading fees go back to the protocol * - Deprecate _updateFor(), index0, index1 because fees go back to the protocol */ contract BaseV2Pair is SolidlyChildImplementation { uint8 public constant decimals = 18; // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 internal constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; uint256 internal constant MINIMUM_LIQUIDITY = 10**3; uint256 internal constant feeDivider = 1e6; /** * @dev storage slots start here */ // simple re-entrancy check uint256 internal _unlocked = 1; string public name; string public symbol; // Used to denote stable or volatile pair, bool public stable; uint256 public feeRatio; uint256 public totalSupply = 0; mapping(address => mapping(address => uint256)) public allowance; mapping(address => uint256) public balanceOf; bytes32 internal DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; address public token0; address public token1; address public fees; address factory; // Structure to capture time period obervations every 30 minutes, used for local oracles struct Observation { uint256 timestamp; uint256 reserve0Cumulative; uint256 reserve1Cumulative; } // Capture oracle reading every 30 minutes uint256 constant periodSize = 1800; Observation[] public observations; uint256 internal decimals0; uint256 internal decimals1; uint256 public reserve0; uint256 public reserve1; uint256 public blockTimestampLast; uint256 public reserve0CumulativeLast; uint256 public reserve1CumulativeLast; event Fees(address indexed sender, uint256 amount0, uint256 amount1); event Mint(address indexed sender, uint256 amount0, uint256 amount1); event Burn( address indexed sender, uint256 amount0, uint256 amount1, address indexed to ); event Swap( address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to ); event Sync(uint256 reserve0, uint256 reserve1); event Claim( address indexed sender, address indexed recipient, uint256 amount0, uint256 amount1 ); event Transfer(address indexed from, address indexed to, uint256 amount); event Approval( address indexed owner, address indexed spender, uint256 amount ); // simple re-entrancy check modifier lock() { require(_unlocked == 1); _unlocked = 2; _; _unlocked = 1; } function initialize( address _token0, address _token1, bool _stable ) external onlyFactory notInitialized { _unlocked = 1; factory = msg.sender; (token0, token1, stable) = (_token0, _token1, _stable); fees = BaseV2Factory(msg.sender).createFees(); if (_stable) { name = string( abi.encodePacked( "StableV2 AMM - ", erc20(_token0).symbol(), "/", erc20(_token1).symbol() ) ); symbol = string( abi.encodePacked( "sAMM-", erc20(_token0).symbol(), "/", erc20(_token1).symbol() ) ); } else { name = string( abi.encodePacked( "VolatileV2 AMM - ", erc20(_token0).symbol(), "/", erc20(_token1).symbol() ) ); symbol = string( abi.encodePacked( "vAMM-", erc20(_token0).symbol(), "/", erc20(_token1).symbol() ) ); } decimals0 = 10**erc20(_token0).decimals(); decimals1 = 10**erc20(_token1).decimals(); observations.push(Observation(block.timestamp, 0, 0)); syncFees(); } function observationLength() external view returns (uint256) { return observations.length; } function lastObservation() public view returns (Observation memory) { return observations[observations.length - 1]; } function metadata() external view returns ( uint256 dec0, uint256 dec1, uint256 r0, uint256 r1, bool st, address t0, address t1, uint256 _feeRatio ) { return ( decimals0, decimals1, reserve0, reserve1, stable, token0, token1, feeRatio ); } function tokens() external view returns (address, address) { return (token0, token1); } /** * @notice directs the fees toward the gauge if it exists, goes to common pool if not */ function claimFees() external returns (uint256 claimed0, uint256 claimed1) { // Determine whether gauge exists IBaseV2Voter voter = IBaseV2Voter(BaseV2Factory(factory).voter()); address feeDistAddress = voter.feeDists(address(this)); bool gaugeExists = feeDistAddress != address(0); if (!gaugeExists) { feeDistAddress = voter.generalFees(); } require( msg.sender == feeDistAddress, "Only feeDist or only general fees if gauge doesn't exist" ); // Sending directly instead of calling notifyRewardAmount(), // relying on the assumption that this method is only callable by feeDists and generalFees // and that those contracts will deal with the accounting properly address _fees = fees; claimed0 = erc20(token0).balanceOf(_fees); claimed1 = erc20(token1).balanceOf(_fees); BaseV2Fees(_fees).claimFeesFor(msg.sender, claimed0, claimed1); emit Claim(msg.sender, msg.sender, claimed0, claimed1); } /** * @notice Accrue fees on token0 * @dev v2 does not record indexes since all fees go back to the protocol */ function _update0(uint256 amount) internal { _safeTransfer(token0, fees, amount); // transfer the fees out to BaseV2Fees emit Fees(msg.sender, amount, 0); } /** * @notice Accrue fees on token1 * @dev v2 does not record indexes since all fees go back to the protocol */ function _update1(uint256 amount) internal { _safeTransfer(token1, fees, amount); // transfer the fees out to BaseV2Fees emit Fees(msg.sender, amount, 0); } function getReserves() public view returns ( uint256 _reserve0, uint256 _reserve1, uint256 _blockTimestampLast ) { _reserve0 = reserve0; _reserve1 = reserve1; _blockTimestampLast = blockTimestampLast; } // update reserves and, on the first call per block, price accumulators function _update( uint256 balance0, uint256 balance1, uint256 _reserve0, uint256 _reserve1 ) internal { uint256 blockTimestamp = block.timestamp; uint256 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { reserve0CumulativeLast += _reserve0 * timeElapsed; reserve1CumulativeLast += _reserve1 * timeElapsed; } Observation memory _point = lastObservation(); timeElapsed = blockTimestamp - _point.timestamp; // compare the last observation with current timestamp, if greater than 30 minutes, record a new event if (timeElapsed > periodSize) { observations.push( Observation( blockTimestamp, reserve0CumulativeLast, reserve1CumulativeLast ) ); } reserve0 = balance0; reserve1 = balance1; blockTimestampLast = blockTimestamp; emit Sync(reserve0, reserve1); } // produces the cumulative price using counterfactuals to save gas and avoid a call to sync. function currentCumulativePrices() public view returns ( uint256 reserve0Cumulative, uint256 reserve1Cumulative, uint256 blockTimestamp ) { blockTimestamp = block.timestamp; reserve0Cumulative = reserve0CumulativeLast; reserve1Cumulative = reserve1CumulativeLast; // if time has elapsed since the last update on the pair, mock the accumulated price values ( uint256 _reserve0, uint256 _reserve1, uint256 _blockTimestampLast ) = getReserves(); if (_blockTimestampLast != blockTimestamp) { // subtraction overflow is desired uint256 timeElapsed = blockTimestamp - _blockTimestampLast; reserve0Cumulative += _reserve0 * timeElapsed; reserve1Cumulative += _reserve1 * timeElapsed; } } // gives the current twap price measured from amountIn * tokenIn gives amountOut function current(address tokenIn, uint256 amountIn) external view returns (uint256 amountOut) { Observation memory _observation = lastObservation(); ( uint256 reserve0Cumulative, uint256 reserve1Cumulative, ) = currentCumulativePrices(); if (block.timestamp == _observation.timestamp) { _observation = observations[observations.length - 2]; } uint256 timeElapsed = block.timestamp - _observation.timestamp; uint256 _reserve0 = (reserve0Cumulative - _observation.reserve0Cumulative) / timeElapsed; uint256 _reserve1 = (reserve1Cumulative - _observation.reserve1Cumulative) / timeElapsed; amountOut = _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1); } // as per `current`, however allows user configured granularity, up to the full window size function quote( address tokenIn, uint256 amountIn, uint256 granularity ) external view returns (uint256 amountOut) { uint256[] memory _prices = sample(tokenIn, amountIn, granularity, 1); uint256 priceAverageCumulative; for (uint256 i = 0; i < _prices.length; i++) { priceAverageCumulative += _prices[i]; } return priceAverageCumulative / granularity; } // returns a memory set of twap prices function prices( address tokenIn, uint256 amountIn, uint256 points ) external view returns (uint256[] memory) { return sample(tokenIn, amountIn, points, 1); } function sample( address tokenIn, uint256 amountIn, uint256 points, uint256 window ) public view returns (uint256[] memory) { uint256[] memory _prices = new uint256[](points); uint256 length = observations.length - 1; uint256 i = length - (points * window); uint256 nextIndex = 0; uint256 index = 0; for (; i < length; i += window) { nextIndex = i + window; uint256 timeElapsed = observations[nextIndex].timestamp - observations[i].timestamp; uint256 _reserve0 = (observations[nextIndex].reserve0Cumulative - observations[i].reserve0Cumulative) / timeElapsed; uint256 _reserve1 = (observations[nextIndex].reserve1Cumulative - observations[i].reserve1Cumulative) / timeElapsed; _prices[index] = _getAmountOut( amountIn, tokenIn, _reserve0, _reserve1 ); index = index + 1; } return _prices; } // this low-level function should be called from a contract which performs important safety checks // standard uniswap v2 implementation function mint(address to) external lock returns (uint256 liquidity) { (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); uint256 _balance0 = erc20(token0).balanceOf(address(this)); uint256 _balance1 = erc20(token1).balanceOf(address(this)); uint256 _amount0 = _balance0 - _reserve0; uint256 _amount1 = _balance1 - _reserve1; uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee if (_totalSupply == 0) { liquidity = Math.sqrt(_amount0 * _amount1) - MINIMUM_LIQUIDITY; _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens } else { liquidity = Math.min( (_amount0 * _totalSupply) / _reserve0, (_amount1 * _totalSupply) / _reserve1 ); } require(liquidity > 0, "ILM"); // BaseV2: INSUFFICIENT_LIQUIDITY_MINTED _mint(to, liquidity); _update(_balance0, _balance1, _reserve0, _reserve1); emit Mint(msg.sender, _amount0, _amount1); } // this low-level function should be called from a contract which performs important safety checks // standard uniswap v2 implementation function burn(address to) external lock returns (uint256 amount0, uint256 amount1) { (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); (address _token0, address _token1) = (token0, token1); uint256 _balance0 = erc20(_token0).balanceOf(address(this)); uint256 _balance1 = erc20(_token1).balanceOf(address(this)); uint256 _liquidity = balanceOf[address(this)]; uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee amount0 = (_liquidity * _balance0) / _totalSupply; // using balances ensures pro-rata distribution amount1 = (_liquidity * _balance1) / _totalSupply; // using balances ensures pro-rata distribution require(amount0 > 0 && amount1 > 0, "ILB"); // BaseV2: INSUFFICIENT_LIQUIDITY_BURNED _burn(address(this), _liquidity); _safeTransfer(_token0, to, amount0); _safeTransfer(_token1, to, amount1); _balance0 = erc20(_token0).balanceOf(address(this)); _balance1 = erc20(_token1).balanceOf(address(this)); _update(_balance0, _balance1, _reserve0, _reserve1); emit Burn(msg.sender, amount0, amount1, to); } // this low-level function should be called from a contract which performs important safety checks function swap( uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data ) external lock { require(!BaseV2Factory(factory).isPaused(), "Paused"); require(amount0Out > 0 || amount1Out > 0, "IOA"); // BaseV2: INSUFFICIENT_OUTPUT_AMOUNT (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); require(amount0Out < _reserve0 && amount1Out < _reserve1, "IL"); // BaseV2: INSUFFICIENT_LIQUIDITY uint256 _balance0; uint256 _balance1; { // scope for _token{0,1}, avoids stack too deep errors (address _token0, address _token1) = (token0, token1); require(to != _token0 && to != _token1, "IT"); // BaseV2: INVALID_TO if (amount0Out > 0) { _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens } if (amount1Out > 0) { _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens } if (data.length > 0) { IBaseV2Callee(to).hook( msg.sender, amount0Out, amount1Out, data ); // callback, used for flash loans } _balance0 = erc20(_token0).balanceOf(address(this)); _balance1 = erc20(_token1).balanceOf(address(this)); } uint256 amount0In = _balance0 > _reserve0 - amount0Out ? _balance0 - (_reserve0 - amount0Out) : 0; uint256 amount1In = _balance1 > _reserve1 - amount1Out ? _balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, "IIA"); // BaseV2: INSUFFICIENT_INPUT_AMOUNT { // scope for reserve{0,1}Adjusted, avoids stack too deep errors (address _token0, address _token1) = (token0, token1); /** * @dev uses gasleft() as a pseudo-random number. Deterministic behaviour here is actually * good, since it means gas usage won't flucuate with time or blocknumber/hash */ // if (gasleft() % 250 == 0) { // syncFees(); // } if (amount0In > 0) { _update0((amount0In * feeRatio) / feeDivider); // accrue fees for token0 and move them out of pool } if (amount1In > 0) { _update1((amount1In * feeRatio) / feeDivider); // accrue fees for token1 and move them out of pool } _balance0 = erc20(_token0).balanceOf(address(this)); // since we removed tokens, we need to reconfirm balances, can also simply use previous balance - fee, but doing balanceOf again as safety check _balance1 = erc20(_token1).balanceOf(address(this)); // The curve, either x3y+y3x for stable pools, or x*y for volatile pools require(_k(_balance0, _balance1) >= _k(_reserve0, _reserve1), "K"); // BaseV2: K } _update(_balance0, _balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } /** * @notice Syncs fees from pair factory */ function syncFees() public { feeRatio = BaseV2Factory(factory).poolFees(address(this)); } // force balances to match reserves function skim(address to) external lock { (address _token0, address _token1) = (token0, token1); _safeTransfer( _token0, to, erc20(_token0).balanceOf(address(this)) - (reserve0) ); _safeTransfer( _token1, to, erc20(_token1).balanceOf(address(this)) - (reserve1) ); } // force reserves to match balances function sync() external lock { _update( erc20(token0).balanceOf(address(this)), erc20(token1).balanceOf(address(this)), reserve0, reserve1 ); } function _f(uint256 x0, uint256 y) internal pure returns (uint256) { return (x0 * ((((y * y) / 1e18) * y) / 1e18)) / 1e18 + (((((x0 * x0) / 1e18) * x0) / 1e18) * y) / 1e18; } function _d(uint256 x0, uint256 y) internal pure returns (uint256) { return (3 * x0 * ((y * y) / 1e18)) / 1e18 + ((((x0 * x0) / 1e18) * x0) / 1e18); } function _get_y( uint256 x0, uint256 xy, uint256 y ) internal pure returns (uint256) { for (uint256 i = 0; i < 255; i++) { uint256 y_prev = y; uint256 k = _f(x0, y); if (k < xy) { uint256 dy = ((xy - k) * 1e18) / _d(x0, y); y = y + dy; } else { uint256 dy = ((k - xy) * 1e18) / _d(x0, y); y = y - dy; } if (y > y_prev) { if (y - y_prev <= 1) { return y; } } else { if (y_prev - y <= 1) { return y; } } } return y; } function getAmountOut(uint256 amountIn, address tokenIn) external view returns (uint256) { (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); amountIn -= (amountIn * feeRatio) / feeDivider; // remove fee from amount received return _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1); } function _getAmountOut( uint256 amountIn, address tokenIn, uint256 _reserve0, uint256 _reserve1 ) internal view returns (uint256) { if (stable) { uint256 xy = _k(_reserve0, _reserve1); _reserve0 = (_reserve0 * 1e18) / decimals0; _reserve1 = (_reserve1 * 1e18) / decimals1; (uint256 reserveA, uint256 reserveB) = tokenIn == token0 ? (_reserve0, _reserve1) : (_reserve1, _reserve0); amountIn = tokenIn == token0 ? (amountIn * 1e18) / decimals0 : (amountIn * 1e18) / decimals1; uint256 y = reserveB - _get_y(amountIn + reserveA, xy, reserveB); return (y * (tokenIn == token0 ? decimals1 : decimals0)) / 1e18; } else { (uint256 reserveA, uint256 reserveB) = tokenIn == token0 ? (_reserve0, _reserve1) : (_reserve1, _reserve0); return (amountIn * reserveB) / (reserveA + amountIn); } } function _k(uint256 x, uint256 y) internal view returns (uint256) { if (stable) { uint256 _x = (x * 1e18) / decimals0; uint256 _y = (y * 1e18) / decimals1; uint256 _a = (_x * _y) / 1e18; uint256 _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18); return (_a * _b) / 1e18; // x3y+y3x >= k } else { return x * y; // xy >= k } } function _mint(address dst, uint256 amount) internal { totalSupply += amount; balanceOf[dst] += amount; emit Transfer(address(0), dst, amount); } function _burn(address dst, uint256 amount) internal { totalSupply -= amount; balanceOf[dst] -= amount; emit Transfer(dst, address(0), amount); } function approve(address spender, uint256 amount) external returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { require(deadline >= block.timestamp, "BaseV2: EXPIRED"); DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); bytes32 digest = keccak256( abi.encodePacked( "\\x19\\x01", DOMAIN_SEPARATOR, keccak256( abi.encode( PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline ) ) ) ); address recoveredAddress = ecrecover(digest, v, r, s); require( recoveredAddress != address(0) && recoveredAddress == owner, "BaseV2: INVALID_SIGNATURE" ); allowance[owner][spender] = value; emit Approval(owner, spender, value); } function transfer(address dst, uint256 amount) external returns (bool) { _transferTokens(msg.sender, dst, amount); return true; } function transferFrom( address src, address dst, uint256 amount ) external returns (bool) { address spender = msg.sender; uint256 spenderAllowance = allowance[src][spender]; if (spender != src && spenderAllowance != type(uint256).max) { uint256 newAllowance = spenderAllowance - amount; allowance[src][spender] = newAllowance; emit Approval(src, spender, newAllowance); } _transferTokens(src, dst, amount); return true; } function _transferTokens( address src, address dst, uint256 amount ) internal { balanceOf[src] -= amount; balanceOf[dst] += amount; emit Transfer(src, dst, amount); } function _safeTransfer( address token, address to, uint256 value ) internal { require(token.code.length > 0, "!contract"); (bool success, bytes memory data) = token.call( abi.encodeWithSelector(erc20.transfer.selector, to, value) ); require( success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: safeTransfer low-level call failed" ); } } /** * @dev Changelog: * - Deprecate constructor with initialize() * - Deprecate pauser role with onlyGovernance * - Deprecate _temp, _temp0, _temp1, and getInitializable() * - Split out feesFactory for subimplementation proxy pattern * - Added records for feesFactory and voter * - Added stable, volatile fees and setter methods */ contract BaseV2Factory is SolidlyFactory { bool public isPaused; address public feesFactory; address public voter; mapping(address => mapping(address => mapping(bool => address))) public getPair; address[] public allPairs; mapping(address => bool) public isPair; // simplified check if its a pair, given that `stable` flag might not be available in peripherals uint256 public maxFees; // 1_000_000 = 100% uint256 public stableFees; uint256 public volatileFees; mapping(address => bool) public poolSpecificFeesEnabled; mapping(address => uint256) public poolSpecificFees; mapping(address => bool) public isOperator; /**************************************** Events ****************************************/ event OperatorStatus(address indexed operator, bool state); event PairCreated( address indexed token0, address indexed token1, bool stable, address pair, uint256 ); /**************************************** Modifiers ****************************************/ modifier onlyGovernanceOrOperator() { require(isOperator[msg.sender] || msg.sender == governanceAddress()); _; } /**************************************** Initialize ****************************************/ function initialize(address _feesFactory, address _voter) external onlyGovernance notInitialized { feesFactory = _feesFactory; voter = _voter; stableFees = 200; // 0.02% volatileFees = 2000; // 0.20% maxFees = 30000; // 3% } /**************************************** Restricted Methods ****************************************/ /** * @notice Sets operator status * @dev Operators are allowed to pause and set pool fees */ function setOperator(address operator, bool state) external onlyGovernance { if (isOperator[operator] != state) { isOperator[operator] = state; emit OperatorStatus(operator, state); } } function setPause(bool _state) external onlyGovernanceOrOperator { isPaused = _state; } function setMaxFees(uint256 _maxFees) external onlyGovernance { require(_maxFees <= 1e6, "Over 100%"); maxFees = _maxFees; } function setStableFees(uint256 _stableFees) external onlyGovernanceOrOperator { require(_stableFees < maxFees, "Over max fees"); stableFees = _stableFees; } function setVolatileFees(uint256 _volatileFees) external onlyGovernanceOrOperator { require(_volatileFees < maxFees, "Over max fees"); volatileFees = _volatileFees; } /** * @notice Sets specific pool's fees * @dev _enabled needs to be set to true, to differentiate between * pools with 0% fees and pools without specific fees */ function setPoolSpecificFees( address _pool, uint256 _fees, bool _enabled ) external onlyGovernanceOrOperator { require(_fees < maxFees, "Over max fees"); poolSpecificFeesEnabled[_pool] = _enabled; poolSpecificFees[_pool] = _fees; // Sync pool's fees BaseV2Pair(_pool).syncFees(); } /**************************************** View Methods ****************************************/ function allPairsLength() external view returns (uint256) { return allPairs.length; } function pairCodeHash() external pure returns (bytes32) { return keccak256(type(SolidlyChildProxy).creationCode); } /** * @notice Returns fee in basis points for a pool */ function poolFees(address pool) external view returns (uint256) { // Return pool specific fees if enabled if (poolSpecificFeesEnabled[pool]) { return poolSpecificFees[pool]; } // Return volatile fees if not stable if (!BaseV2Pair(pool).stable()) { return volatileFees; } // Return stable fees otherwise return stableFees; } /**************************************** User Interaction ****************************************/ function createPair( address tokenA, address tokenB, bool stable ) external returns (address pair) { require(tokenA != tokenB, "IA"); // BaseV2: IDENTICAL_ADDRESSES (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0), "ZA"); // BaseV2: ZERO_ADDRESS require(getPair[token0][token1][stable] == address(0), "PE"); // BaseV2: PAIR_EXISTS - single check is sufficient bytes32 salt = keccak256(abi.encodePacked(token0, token1, stable)); // notice salt includes stable as well, 3 parameters pair = _deployChildProxyWithSalt(salt); BaseV2Pair(pair).initialize(token0, token1, stable); getPair[token0][token1][stable] = pair; getPair[token1][token0][stable] = pair; // populate mapping in the reverse direction allPairs.push(pair); isPair[pair] = true; emit PairCreated(token0, token1, stable, pair, allPairs.length); } function createFees() external returns (address fees) { fees = BaseV2FeesFactory(feesFactory).createFees(msg.sender); } } /** * @dev Introduced in v2 so each factory only need to carry one set of interface and subimplementation */ contract BaseV2FeesFactory is SolidlyFactory { function createFees(address _pair) external returns (address fees) { fees = _deployChildProxy(); BaseV2Fees(fees).initialize(_pair); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.11; interface IFactory { function governanceAddress() external view returns (address); function childSubImplementationAddress() external view returns (address); function childInterfaceAddress() external view returns (address); } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./SolidlyImplementation.sol"; import "./interfaces/IFactory.sol"; contract SolidlyChildImplementation is SolidlyImplementation { bytes32 constant FACTORY_SLOT = 0x547b500e425d72fd0723933cceefc203cef652b4736fd04250c3369b3e1a0a72; // keccak256('FACTORY') - 1 modifier onlyFactory() { require(msg.sender == factoryAddress(), "only Factory"); _; } /**************************************** VIEW METHODS ****************************************/ /** * @notice Fetch current governance address from factory * @return _governanceAddress Returns current governance address */ function governanceAddress() public view override returns (address _governanceAddress) { return IFactory(factoryAddress()).governanceAddress(); } function factoryAddress() public view returns (address _factory) { assembly { _factory := sload(FACTORY_SLOT) } } } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./SolidlyProxy.sol"; import "./interfaces/IFactory.sol"; /** * @notice Child Proxy deployed by factories for pairs, fees, gauges, and bribes. Calls back to the factory to fetch proxy implementation. */ contract SolidlyChildProxy is SolidlyProxy { bytes32 constant FACTORY_SLOT = 0x547b500e425d72fd0723933cceefc203cef652b4736fd04250c3369b3e1a0a72; // keccak256('FACTORY') - 1 modifier onlyFactory() { require(msg.sender == factoryAddress(), "only Factory"); _; } /** * @notice Records factory address and current interface implementation */ constructor() { address _factory = msg.sender; address _interface = IFactory(msg.sender).childInterfaceAddress(); assembly { sstore(FACTORY_SLOT, _factory) sstore(IMPLEMENTATION_SLOT, _interface) // Storing the interface into EIP-1967's implementation slot so Etherscan picks up the interface } } /**************************************** SETTINGS ****************************************/ /** * @notice Governance callable method to update the Factory address */ function updateFactoryAddress(address _factory) external onlyGovernance { assembly { sstore(FACTORY_SLOT, _factory) } } /** * @notice Publically callable function to sync proxy interface with the one recorded in the factory */ function updateInterfaceAddress() external { address _newInterfaceAddress = IFactory(factoryAddress()) .childInterfaceAddress(); require( implementationAddress() != _newInterfaceAddress, "Nothing to update" ); assembly { sstore(IMPLEMENTATION_SLOT, _newInterfaceAddress) } } /**************************************** VIEW METHODS ****************************************/ /** * @notice Fetch current governance address from factory * @return _governanceAddress Returns current governance address */ function governanceAddress() public view override returns (address _governanceAddress) { return IFactory(factoryAddress()).governanceAddress(); } function factoryAddress() public view returns (address _factory) { assembly { _factory := sload(FACTORY_SLOT) } } /** *@notice Fetch address where actual contract logic is at */ function subImplementationAddress() public view returns (address _subimplementation) { return IFactory(factoryAddress()).childSubImplementationAddress(); } /** * @notice Fetch address where the interface for the contract is */ function interfaceAddress() public view override returns (address _interface) { assembly { _interface := sload(IMPLEMENTATION_SLOT) } } /**************************************** FALLBACK METHODS ****************************************/ /** * @notice Fallback function that delegatecalls the subimplementation instead of what's in the IMPLEMENTATION_SLOT */ function _delegateCallSubimplmentation() internal override { address contractLogic = IFactory(factoryAddress()) .childSubImplementationAddress(); assembly { calldatacopy(0x0, 0x0, calldatasize()) let success := delegatecall( gas(), contractLogic, 0x0, calldatasize(), 0, 0 ) let returnDataSize := returndatasize() returndatacopy(0, 0, returnDataSize) switch success case 0 { revert(0, returnDataSize) } default { return(0, returnDataSize) } } } fallback() external payable override { _delegateCallSubimplmentation(); } receive() external payable override { _delegateCallSubimplmentation(); } } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; import "./SolidlyImplementation.sol"; import "./SolidlyChildProxy.sol"; contract SolidlyFactory is SolidlyImplementation { bytes32 constant CHILD_SUBIMPLEMENTATION_SLOT = 0xa7461aa7cde97eb2572f8234e341359c6baae47e1feeb3c235edffe5f0fc089d; // keccak256('CHILD_SUBIMPLEMENTATION') - 1 bytes32 constant CHILD_INTERFACE_SLOT = 0x23762bb6469fe7a7bd6609262f442817ed09ca1f07add24ef069610d59c90649; // keccak256('CHILD_INTERFACE') - 1 bytes32 constant SUBIMPLEMENTATION_SLOT = 0xa1056f3ed783ff191ada02861fcb19d9ae3a8f50b739813a127951ef5290458d; // keccak256('SUBIMPLEMENTATION') - 1 bytes32 constant INTERFACE_SLOT = 0x4a9bf2931aa5eae439c602abae4bd662e7919244decac463e2e35fc862c5fb98; // keccak256('INTERFACE') - 1 address public interfaceSourceAddress; function _deployChildProxy() internal returns (address) { address addr = address(new SolidlyChildProxy()); return addr; } function _deployChildProxyWithSalt(bytes32 salt) internal returns (address) { address addr = address(new SolidlyChildProxy{salt: salt}()); return addr; } function updateChildSubImplementationAddress( address _childSubImplementationAddress ) external onlyGovernance { assembly { sstore(CHILD_SUBIMPLEMENTATION_SLOT, _childSubImplementationAddress) } } function updateChildInterfaceAddress(address _childInterfaceAddress) external onlyGovernance { assembly { sstore(CHILD_INTERFACE_SLOT, _childInterfaceAddress) } } function childSubImplementationAddress() external view returns (address _childSubImplementation) { assembly { _childSubImplementation := sload(CHILD_SUBIMPLEMENTATION_SLOT) } } function childInterfaceAddress() external view returns (address _childInterface) { assembly { _childInterface := sload(CHILD_INTERFACE_SLOT) } } } // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; /** * @title Solidly+ Implementation * @author Solidly+ * @notice Governable implementation that relies on governance slot to be set by the proxy */ contract SolidlyImplementation { bytes32 constant GOVERNANCE_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; // keccak256('eip1967.proxy.admin') bytes32 constant INITIALIZED_SLOT = 0x834ce84547018237034401a09067277cdcbe7bbf7d7d30f6b382b0a102b7b4a3; // keccak256('eip1967.proxy.initialized') /** * @notice Reverts if msg.sender is not governance */ modifier onlyGovernance() { require(msg.sender == governanceAddress(), "Only governance"); _; } /** * @notice Reverts if contract is already initialized * @dev U4sed by implementations to ensure initialize() is only called once */ modifier notInitialized() { bool initialized; assembly { initialized := sload(INITIALIZED_SLOT) if eq(initialized, 1) { revert(0, 0) } sstore(INITIALIZED_SLOT, 1) } _; } /** * @notice Fetch current governance address * @return _governanceAddress Returns current governance address */ function governanceAddress() public view virtual returns (address _governanceAddress) { assembly { _governanceAddress := sload(GOVERNANCE_SLOT) } } } // SPDX-License-Identifier: BUSL pragma solidity 0.8.11; /** * @title Solidly+ governance killable proxy * @author Solidly+ * @notice EIP-1967 upgradeable proxy with the ability to kill governance and render the contract immutable */ contract SolidlyProxy { bytes32 constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // keccak256('eip1967.proxy.implementation'), actually used for interface so etherscan picks up the interface bytes32 constant LOGIC_SLOT = 0x5942be825425c77e56e4bce97986794ab0f100954e40fc1390ae0e003710a3ab; // keccak256('LOGIC') - 1, actual logic implementation bytes32 constant GOVERNANCE_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; // keccak256('eip1967.proxy.admin') bytes32 constant INITIALIZED_SLOT = 0x834ce84547018237034401a09067277cdcbe7bbf7d7d30f6b382b0a102b7b4a3; // keccak256('eip1967.proxy.initialized') /** * @notice Reverts if msg.sender is not governance */ modifier onlyGovernance() { require(msg.sender == governanceAddress(), "Only governance"); _; } /** * @notice Reverts if contract is already initialized * @dev Used by implementations to ensure initialize() is only called once */ modifier notInitialized() { bool initialized; assembly { initialized := sload(INITIALIZED_SLOT) if eq(initialized, 1) { revert(0, 0) } sstore(INITIALIZED_SLOT, 1) } _; } /** * @notice Sets up deployer as a proxy governance */ constructor() { address _governanceAddress = msg.sender; assembly { sstore(GOVERNANCE_SLOT, _governanceAddress) } } /** * @notice Detect whether or not governance is killed * @return Return true if governance is killed, false if not * @dev If governance is killed this contract becomes immutable */ function governanceIsKilled() public view returns (bool) { return governanceAddress() == address(0); } /** * @notice Kill governance, making this contract immutable * @dev Only governance can kil governance */ function killGovernance() external onlyGovernance { updateGovernanceAddress(address(0)); } /** * @notice Update implementation address * @param _interfaceAddress Address of the new interface * @dev Only governance can update implementation */ function updateInterfaceAddress(address _interfaceAddress) external onlyGovernance { assembly { sstore(IMPLEMENTATION_SLOT, _interfaceAddress) } } /** * @notice Actually updates interface, kept for etherscan pattern recognition * @param _implementationAddress Address of the new implementation * @dev Only governance can update implementation */ function updateImplementationAddress(address _implementationAddress) external onlyGovernance { assembly { sstore(IMPLEMENTATION_SLOT, _implementationAddress) } } /** * @notice Update implementation address * @param _logicAddress Address of the new implementation * @dev Only governance can update implementation */ function updateLogicAddress(address _logicAddress) external onlyGovernance { assembly { sstore(LOGIC_SLOT, _logicAddress) } } /** * @notice Update governance address * @param _governanceAddress New governance address * @dev Only governance can update governance */ function updateGovernanceAddress(address _governanceAddress) public onlyGovernance { assembly { sstore(GOVERNANCE_SLOT, _governanceAddress) } } /** * @notice Fetch the current implementation address * @return _implementationAddress Returns the current implementation address */ function implementationAddress() public view returns (address _implementationAddress) { assembly { _implementationAddress := sload(IMPLEMENTATION_SLOT) } } /** * @notice Fetch the current implementation address * @return _interfaceAddress Returns the current implementation address */ function interfaceAddress() public view virtual returns (address _interfaceAddress) { assembly { _interfaceAddress := sload(IMPLEMENTATION_SLOT) } } /** * @notice Fetch the current implementation address * @return _logicAddress Returns the current implementation address */ function logicAddress() public view virtual returns (address _logicAddress) { assembly { _logicAddress := sload(LOGIC_SLOT) } } /** * @notice Fetch current governance address * @return _governanceAddress Returns current governance address */ function governanceAddress() public view virtual returns (address _governanceAddress) { assembly { _governanceAddress := sload(GOVERNANCE_SLOT) } } /** * @notice Fallback function that delegatecalls the subimplementation instead of what's in the IMPLEMENTATION_SLOT */ function _delegateCallSubimplmentation() internal virtual { assembly { let contractLogic := sload(LOGIC_SLOT) calldatacopy(0x0, 0x0, calldatasize()) let success := delegatecall( gas(), contractLogic, 0x0, calldatasize(), 0, 0 ) let returnDataSize := returndatasize() returndatacopy(0, 0, returnDataSize) switch success case 0 { revert(0, returnDataSize) } default { return(0, returnDataSize) } } } /** * @notice Delegatecall fallback proxy */ fallback() external payable virtual { _delegateCallSubimplmentation(); } receive() external payable virtual { _delegateCallSubimplmentation(); } }