diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe093c1c..60abc929c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Next version +- feat: add `UniSwap` routing logic, manager, and univ3 logics + # 2.1.0-6 - Add view function to `SmartKandel` to get the current logics diff --git a/config.js b/config.js index 5ab0f794f..8ca2e59ca 100644 --- a/config.js +++ b/config.js @@ -22,6 +22,8 @@ exports.abi_exports = [ "AbstractRoutingLogic", "SimpleAaveLogic", "MangroveAmplifier", + "UniswapV3Manager", + "UniswapV3RoutingLogic", "OrbitLogic", "OrbitLogicStorage", "SmartKandelSeeder", diff --git a/copyUniBytecode.js b/copyUniBytecode.js new file mode 100644 index 000000000..358951948 --- /dev/null +++ b/copyUniBytecode.js @@ -0,0 +1,50 @@ +const fs = require("fs"); + +/** + * Outputs the compiler output to a file. + * @typedef {Object} CompilerOutput + * @property {string} bytecode The bytecode of the contract. + */ + +const files = [ + "node_modules/@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json", + "node_modules/@uniswap/v3-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json", + "node_modules/@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json", +]; +const outputs = [ + __dirname + "/uni-out/UniswapV3Factory.txt", + __dirname + "/uni-out/NonfungibleTokenPositionDescriptor.txt", + __dirname + "/uni-out/NonfungiblePositionManager.txt", +]; + +async function main() { + await Promise.all( + files.map(async (file, idx) => { + const fileContent = await new Promise((resolve, reject) => { + fs.readFile(file, "utf8", (err, data) => { + if (err) reject(err); + resolve(data); + }); + }); + /** + * @type {CompilerOutput} + */ + const data = JSON.parse(fileContent); + const bytecode = data.bytecode.replace( + /__\$[a-fA-F0-9]+\$__/gm, + "0".repeat(40), + ); + + await new Promise((resolve, reject) => { + fs.writeFile(outputs[idx], bytecode, (err) => { + if (err) reject(err); + resolve(); + }); + }); + }), + ); +} + +main() + .then(() => console.log("done")) + .catch(console.error); diff --git a/foundry.toml b/foundry.toml index 7e740f869..d0bea73ae 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,7 +8,7 @@ out='out' libs=['lib'] cache_path='cache' evm_version='paris' -fs_permissions = [{ access = "read-write", path = "./addresses/"}, { access = "read-write", path = "./analytics/"}, { access = "read", path = "./out/" }, {access = "read", path = "./node_modules/@mangrovedao/mangrove-core/"}, { access = "read", path = "./mgvConfig.json" }] +fs_permissions = [{ access = "read-write", path = "./addresses/"}, { access = "read-write", path = "./analytics/"}, { access = "read", path = "./out/" }, {access = "read", path = "./node_modules/@mangrovedao/mangrove-core/"}, {access = "read", path = "./uni-out/"}, { access = "read", path = "./mgvConfig.json" }] solc_version="0.8.20" ffi=true optimizer=false diff --git a/package.json b/package.json index 433dd0986..4e2005956 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,10 @@ "prepack": "pinst --disable && yarn build", "postpack": "pinst --enable", "postinstall": "husky install", - "build": "yarn run copyDeploymentAddresses && yarn run copyContextAddresses && forge build && node copyArtifacts && node buildIndex && node checkNatspec", + "build": "yarn run copyDeploymentAddresses && yarn run copyContextAddresses && yarn run copyUniBytecode && forge build && node copyArtifacts && node buildIndex && node checkNatspec", "copyDeploymentAddresses": "node copyDeploymentAddresses", "copyContextAddresses": "node copyContextAddresses", + "copyUniBytecode": "node copyUniBytecode", "clean": "forge clean; rimraf index.js dist", "test": "forge test -vvv", "gas-measurement": "GAS_MATCH_PATH='*.gasreq.*' bash ./node_modules/@mangrovedao/mangrove-core/gas-measurement.sh" @@ -33,7 +34,9 @@ "/README.md" ], "dependencies": { - "@mangrovedao/mangrove-core": "^2.1.1" + "@mangrovedao/mangrove-core": "^2.1.1", + "@uniswap/v3-core": "^1.0.1", + "@uniswap/v3-periphery": "^1.4.4" }, "devDependencies": { "@mangrovedao/context-addresses": "^1.3.4", diff --git a/remappings.txt b/remappings.txt index e04331d6f..41b4413ea 100644 --- a/remappings.txt +++ b/remappings.txt @@ -10,5 +10,12 @@ ds-test/=node_modules/@mangrovedao/mangrove-core/lib/forge-std/lib/ds-test/src @mgv-strats/test/=test/ @mgv-strats/script/=script/ +@uniswap/v3-core/=node_modules/@uniswap/v3-core/ +@uniswap/v2-core/=node_modules/@uniswap/v2-core/ +@uniswap/v3-periphery/=node_modules/@uniswap/v3-periphery/ +@uniswap/lib/=node_modules/@uniswap/lib/ + +base64-sol/=node_modules/base64-sol/ + @openzeppelin/contracts/=lib/openzeppelin/contracts/ -@orbit-protocol/contracts/=lib/orbit-protocol/contracts/ \ No newline at end of file +@orbit-protocol/contracts/=lib/orbit-protocol/contracts/ diff --git a/script/deployers/MumbaiActivateMarket.s.sol b/script/deployers/MumbaiActivateMarket.s.sol index 0f8b9e158..a18a46fbb 100644 --- a/script/deployers/MumbaiActivateMarket.s.sol +++ b/script/deployers/MumbaiActivateMarket.s.sol @@ -32,7 +32,6 @@ import {MgvReader, Market} from "@mgv/src/periphery/MgvReader.sol"; * TOKEN1_IN_USD=$(cast ff 8 0.9) \ * forge script --fork-url mumbai MumbaiActivateMarket */ - contract MumbaiActivateMarket is Deployer { uint maticPrice; @@ -74,10 +73,7 @@ contract MumbaiActivateMarket is Deployer { tokens[0] = IERC20(market.tkn0); tokens[1] = IERC20(market.tkn1); - new ActivateMangroveOrder().innerRun({ - mgvOrder: mangroveOrder, - tokens: tokens - }); + new ActivateMangroveOrder().innerRun({mgvOrder: mangroveOrder, tokens: tokens}); } function smokeTest(Market memory market) internal view { diff --git a/script/strategies/kandel/KandelShutdown.s.sol b/script/strategies/kandel/KandelShutdown.s.sol index 396ee8e0c..e9d2ad369 100644 --- a/script/strategies/kandel/KandelShutdown.s.sol +++ b/script/strategies/kandel/KandelShutdown.s.sol @@ -11,7 +11,6 @@ import {toFixed} from "@mgv/lib/Test2.sol"; /** * @notice Populate Kandel's distribution on Mangrove */ - contract KandelShutdown is Deployer { function run() public { innerRun({kdl: GeometricKandel(envAddressOrName("KANDEL"))}); diff --git a/script/strategies/kandel/KandelSower.s.sol b/script/strategies/kandel/KandelSower.s.sol index 7056ea433..a9ad66623 100644 --- a/script/strategies/kandel/KandelSower.s.sol +++ b/script/strategies/kandel/KandelSower.s.sol @@ -17,7 +17,6 @@ import {console2 as console} from "@mgv/forge-std/Script.sol"; * @dev since the max number of price slot Kandel can use is an immutable, one should deploy Kandel on a large price range. * @dev Example: WRITE_DEPLOY=true ON_AAVE=true SHARING=false BASE=CRV QUOTE=WBTC TICK_SPACING=1 forge script --fork-url $LOCALHOST_URL KandelSower --broadcast --private-key $MUMBAI_PRIVATE_KEY */ - contract KandelSower is Deployer, MangroveTest { function run() public { bool onAave = vm.envBool("ON_AAVE"); diff --git a/script/strategies/kandel/KandelStatus.s.sol b/script/strategies/kandel/KandelStatus.s.sol index 36ade607a..eab0c4de1 100644 --- a/script/strategies/kandel/KandelStatus.s.sol +++ b/script/strategies/kandel/KandelStatus.s.sol @@ -12,7 +12,6 @@ import {toFixed} from "@mgv/lib/Test2.sol"; /** * @notice Populate Kandel's distribution on Mangrove */ - contract KandelStatus is Deployer { function run() public view { innerRun({kdl: GeometricKandel(envAddressOrName("KANDEL"))}); diff --git a/script/strategies/kandel/deployers/KandelDeployer.s.sol b/script/strategies/kandel/deployers/KandelDeployer.s.sol index 57ee23651..3a9e85f43 100644 --- a/script/strategies/kandel/deployers/KandelDeployer.s.sol +++ b/script/strategies/kandel/deployers/KandelDeployer.s.sol @@ -13,7 +13,6 @@ import {KandelSower} from "../KandelSower.s.sol"; * @dev since the max number of price slot Kandel can use is an immutable, one should deploy Kandel on a large price range. * @dev Example: WRITE_DEPLOY=true BASE=WETH QUOTE=USDC forge script --fork-url $LOCALHOST_URL KandelDeployer --broadcast --private-key $MUMBAI_PRIVATE_KEY */ - contract KandelDeployer is Deployer { Kandel public current; diff --git a/script/strategies/routing_logic/deployers/uni-v3/BlastUniV3RoutingLogicDeployer.s.sol b/script/strategies/routing_logic/deployers/uni-v3/BlastUniV3RoutingLogicDeployer.s.sol new file mode 100644 index 000000000..6e90a1b6d --- /dev/null +++ b/script/strategies/routing_logic/deployers/uni-v3/BlastUniV3RoutingLogicDeployer.s.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Script, console} from "@mgv/forge-std/Script.sol"; +import {BlastUniswapV3Manager} from "@mgv-strats/src/strategies/chains/blast/routing_logic/BlastUniswapV3Manager.sol"; +import {INonfungiblePositionManager} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol"; +import {RouterProxyFactory} from "@mgv-strats/src/strategies/routers/RouterProxyFactory.sol"; +import {AbstractRouter} from "@mgv-strats/src/strategies/routers/abstract/AbstractRouter.sol"; +import {UniswapV3RoutingLogic} from + "@mgv-strats/src/strategies/routing_logic/restaking/uni-v3/UniswapV3RoutingLogic.sol"; +import {Deployer} from "@mgv/script/lib/Deployer.sol"; +import {IERC20Rebasing} from "@mgv-strats/src/strategies/vendor/blast/IERC20Rebasing.sol"; +import {IBlast} from "@mgv/src/chains/blast/interfaces/IBlast.sol"; +import {IBlastPoints} from "@mgv/src/chains/blast/interfaces/IBlastPoints.sol"; + +/* Deploys a UniswapV3Logic instance */ +contract BlastUniV3RoutingLogicDeployer is Deployer { + function run() public { + address[] memory tokens = vm.envAddress("REBASING_TOKENS", ","); + IERC20Rebasing[] memory rebasingTokens = new IERC20Rebasing[](tokens.length); + for (uint i = 0; i < tokens.length; i++) { + rebasingTokens[i] = IERC20Rebasing(tokens[i]); + } + innerRun({ + positionManager: INonfungiblePositionManager(vm.envAddress("UNISWAP_V3_POSITION_MANAGER")), + factory: RouterProxyFactory(envAddressOrName("ROUTER_PROXY_FACTORY", "RouterProxyFactory")), + implementation: AbstractRouter(envAddressOrName("SMART_ROUTER_IMPLEMENTATION", "MangroveOrder-Router")), + forkName: vm.envString("FORK_NAME"), + tokens: rebasingTokens, + admin: vm.envAddress("ADMIN"), + blastContract: IBlast(vm.envAddress("BLAST")), + pointsContract: IBlastPoints(vm.envAddress("BLAST_POINTS")), + pointsOperator: vm.envAddress("BLAST_POINTS_OPERATOR"), + blastGovernor: vm.envAddress("BLAST_GOVERNOR") + }); + outputDeployment(); + } + + function innerRun( + INonfungiblePositionManager positionManager, + RouterProxyFactory factory, + AbstractRouter implementation, + string memory forkName, + IERC20Rebasing[] memory tokens, + address admin, + IBlast blastContract, + IBlastPoints pointsContract, + address pointsOperator, + address blastGovernor + ) public { + broadcast(); + BlastUniswapV3Manager uniswapV3Manager = new BlastUniswapV3Manager( + tokens, + admin, + positionManager, + factory, + implementation, + pointsContract, + pointsOperator, + blastContract, + blastGovernor + ); + string memory managerName = string.concat("UniswapV3Manager-", forkName); + fork.set(managerName, address(uniswapV3Manager)); + console.log("UniswapV3Manager deployed", address(uniswapV3Manager)); + + broadcast(); + UniswapV3RoutingLogic uniswapV3RoutingLogic = new UniswapV3RoutingLogic(uniswapV3Manager); + string memory logicName = string.concat("UniswapV3RoutingLogic-", forkName); + fork.set(logicName, address(uniswapV3RoutingLogic)); + console.log("UniswapV3RoutingLogic deployed", address(uniswapV3RoutingLogic)); + } +} diff --git a/script/strategies/routing_logic/deployers/uni-v3/UniV3RoutingLogicDeployer.s.sol b/script/strategies/routing_logic/deployers/uni-v3/UniV3RoutingLogicDeployer.s.sol new file mode 100644 index 000000000..3d7023d61 --- /dev/null +++ b/script/strategies/routing_logic/deployers/uni-v3/UniV3RoutingLogicDeployer.s.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Script, console} from "@mgv/forge-std/Script.sol"; +import {UniswapV3Manager} from "@mgv-strats/src/strategies/routing_logic/restaking/uni-v3/UniswapV3Manager.sol"; +import {INonfungiblePositionManager} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol"; +import {RouterProxyFactory} from "@mgv-strats/src/strategies/routers/RouterProxyFactory.sol"; +import {AbstractRouter} from "@mgv-strats/src/strategies/routers/abstract/AbstractRouter.sol"; +import {UniswapV3RoutingLogic} from + "@mgv-strats/src/strategies/routing_logic/restaking/uni-v3/UniswapV3RoutingLogic.sol"; +import {Deployer} from "@mgv/script/lib/Deployer.sol"; + +/* Deploys a UniswapV3Logic instance */ +contract UniV3RoutingLogicDeployer is Deployer { + function run() public { + innerRun({ + positionManager: INonfungiblePositionManager(vm.envAddress("UNISWAP_V3_POSITION_MANAGER")), + factory: RouterProxyFactory(envAddressOrName("ROUTER_PROXY_FACTORY", "RouterProxyFactory")), + implementation: AbstractRouter(envAddressOrName("SMART_ROUTER_IMPLEMENTATION", "MangroveOrder-Router")), + forkName: vm.envString("FORK_NAME") + }); + outputDeployment(); + } + + function innerRun( + INonfungiblePositionManager positionManager, + RouterProxyFactory factory, + AbstractRouter implementation, + string memory forkName + ) public { + broadcast(); + UniswapV3Manager uniswapV3Manager = new UniswapV3Manager(positionManager, factory, implementation); + string memory managerName = string.concat("UniswapV3Manager-", forkName); + fork.set(managerName, address(uniswapV3Manager)); + console.log("UniswapV3Manager deployed", address(uniswapV3Manager)); + + broadcast(); + UniswapV3RoutingLogic uniswapV3RoutingLogic = new UniswapV3RoutingLogic(uniswapV3Manager); + string memory logicName = string.concat("UniswapV3RoutingLogic-", forkName); + fork.set(logicName, address(uniswapV3RoutingLogic)); + console.log("UniswapV3RoutingLogic deployed", address(uniswapV3RoutingLogic)); + } +} diff --git a/src/strategies/chains/blast/routing_logic/BlastUniswapV3Manager.sol b/src/strategies/chains/blast/routing_logic/BlastUniswapV3Manager.sol new file mode 100644 index 000000000..d9d558ec9 --- /dev/null +++ b/src/strategies/chains/blast/routing_logic/BlastUniswapV3Manager.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { + UniswapV3Manager, + INonfungiblePositionManager, + RouterProxyFactory, + AbstractRouter +} from "@mgv-strats/src/strategies/routing_logic/restaking/uni-v3/UniswapV3Manager.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC20Rebasing, YieldMode} from "@mgv-strats/src/strategies/vendor/blast/IERC20Rebasing.sol"; +import {IBlastPoints} from "@mgv/src/chains/blast/interfaces/IBlastPoints.sol"; +import {IBlast} from "@mgv/src/chains/blast/interfaces/IBlast.sol"; + +/// @title BlastUniswapV3Manager +/// @author Mangrove +/// @notice A UniswapV3Manager that can handle rebasing tokens from Blast +contract BlastUniswapV3Manager is UniswapV3Manager, Ownable { + /// @notice Constructor + /// @param _initTokens The tokens to initialize + /// @param admin the admin address + /// @param positionManager the position manager + /// @param factory the router proxy factory + /// @param implementation the router implementation + constructor( + IERC20Rebasing[] memory _initTokens, + address admin, + INonfungiblePositionManager positionManager, + RouterProxyFactory factory, + AbstractRouter implementation, + IBlastPoints pointsContract, + address pointsOperator, + IBlast blastContract, + address blastGovernor + ) UniswapV3Manager(positionManager, factory, implementation) Ownable(admin) { + for (uint i = 0; i < _initTokens.length; i++) { + _initRebasingToken(_initTokens[i]); + } + pointsContract.configurePointsOperator(pointsOperator); + blastContract.configureClaimableGas(); + blastContract.configureGovernor(blastGovernor); + } + + /// @notice Initializes a rebasing token with the correct yield mode + /// @param token The token to configure + function _initRebasingToken(IERC20Rebasing token) internal { + token.configure(YieldMode.CLAIMABLE); + } + + /// @notice Initializes a rebasing token with the correct yield mode + /// @param token The token to configure + function initRebasingToken(IERC20Rebasing token) external onlyOwner { + _initRebasingToken(token); + } + + /// @notice Claims yield from a rebasing token + /// @param token The token to claim from + /// @param recipient The recipient of the claim + /// @param amount The amount to claim + function claim(IERC20Rebasing token, address recipient, uint amount) external onlyOwner { + token.claim(recipient, amount); + } +} diff --git a/src/strategies/offer_maker/market_making/kandel/AaveKandelSeeder.sol b/src/strategies/offer_maker/market_making/kandel/AaveKandelSeeder.sol index 6765cfe86..8b44d06b9 100644 --- a/src/strategies/offer_maker/market_making/kandel/AaveKandelSeeder.sol +++ b/src/strategies/offer_maker/market_making/kandel/AaveKandelSeeder.sol @@ -51,11 +51,16 @@ contract AaveKandelSeeder is AbstractKandelSeeder { // allowing owner to be modified by Kandel's admin would require approval from owner's address controller address owner = liquiditySharing ? msg.sender : address(0); - kandel = new AaveKandel(MGV, olKeyBaseQuote, KANDEL_GASREQ, Direct.RouterParams({ - routerImplementation: AAVE_ROUTER, // using aave pooled router to source liquidity - fundOwner: owner, - strict: liquiditySharing - })); + kandel = new AaveKandel( + MGV, + olKeyBaseQuote, + KANDEL_GASREQ, + Direct.RouterParams({ + routerImplementation: AAVE_ROUTER, // using aave pooled router to source liquidity + fundOwner: owner, + strict: liquiditySharing + }) + ); // Allowing newly deployed Kandel to bind to the AaveRouter AAVE_ROUTER.bind(address(kandel)); emit NewAaveKandel(msg.sender, olKeyBaseQuote.hash(), olKeyBaseQuote.flipped().hash(), address(kandel), owner); diff --git a/src/strategies/routing_logic/orbit/OrbitLogic.sol b/src/strategies/routing_logic/orbit/OrbitLogic.sol index edd7c0cec..5c9ab29d7 100644 --- a/src/strategies/routing_logic/orbit/OrbitLogic.sol +++ b/src/strategies/routing_logic/orbit/OrbitLogic.sol @@ -40,7 +40,7 @@ contract OrbitLogic is AbstractRoutingLogic, ExponentialNoError { returns (uint pulled) { OErc20 overlyingToken = overlying(token); - // compute amout to be pulled (currently taking stored axchange rate and not the current one) + // compute amout to be pulled (currently taking stored exchange rate and not the current one) Exp memory exchangeRate = Exp({mantissa: overlyingToken.exchangeRateStored()}); uint toPull = div_(amount, exchangeRate); // Pull the amount of oTokens from the fundOwner @@ -50,7 +50,7 @@ contract OrbitLogic is AbstractRoutingLogic, ExponentialNoError { ); // redeem the oTokens to get the underlying tokens overlyingToken.redeemUnderlying(amount); - // send the underlying tokens to the fundOwner + // send the underlying tokens to the msg.sender (maker contract) pulled = token.balanceOf(address(this)); require(TransferLib.transferToken(token, msg.sender, pulled), "OrbitLogic: Transfer failed"); uint balance = overlyingToken.balanceOf(address(this)); diff --git a/src/strategies/routing_logic/restaking/uni-v3/UniswapV3Manager.sol b/src/strategies/routing_logic/restaking/uni-v3/UniswapV3Manager.sol new file mode 100644 index 000000000..0433c8132 --- /dev/null +++ b/src/strategies/routing_logic/restaking/uni-v3/UniswapV3Manager.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {INonfungiblePositionManager} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol"; +import {IERC20} from "@mgv/lib/IERC20.sol"; +import {TransferLib} from "@mgv/lib/TransferLib.sol"; +import {AbstractRouter} from "@mgv-strats/src/strategies/routers/abstract/AbstractRouter.sol"; +import {RouterProxyFactory} from "@mgv-strats/src/strategies/routers/RouterProxyFactory.sol"; +import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +/// @title Uniswap V3 Manager +/// @author Mangrove DAO +/// @notice This contract is used to manage Uniswap V3 positions used by the mangrove strategy +/// * This contract holds the position ID for the managed positions +/// * It also holds the balances of the tokens that cannot be reinvested immediately into the strategy +contract UniswapV3Manager is ERC1155("") { + /// @notice The position manager contract + INonfungiblePositionManager public immutable positionManager; + + /// @notice The router implementation contract + AbstractRouter public immutable ROUTER_IMPLEMENTATION; + + /// @notice The router proxy factory contract + RouterProxyFactory public immutable routerProxyFactory; + + /// @notice The positions mapping + mapping(address owner => uint position) public positions; + + /// @notice Fires when a position is changed + /// @param user the user address + /// @param positionId the position ID + event PositionChanged(address indexed user, uint indexed positionId); + + /// @notice Modifier to allow only the user or a manager to call a function + /// @param _user the user address + modifier onlyAllowed(address _user) { + require(_user == msg.sender || isApprovedForAll(_user, msg.sender), "UniV3Manager/not-allowed"); + _; + } + + /// @notice Modifier to allow only the user router to call a function + /// @param _user the user address + modifier onlyUserRouter(address _user) { + require(userRouter(_user) == msg.sender, "UniV3Manager/not-allowed"); + _; + } + + /// @notice Contract constructor + /// @param _positionManager the position manager contract + /// @param _routerProxyFactory the router proxy factory contract + /// @param _routerImplementation the router implementation contract + constructor( + INonfungiblePositionManager _positionManager, + RouterProxyFactory _routerProxyFactory, + AbstractRouter _routerImplementation + ) { + positionManager = _positionManager; + routerProxyFactory = _routerProxyFactory; + ROUTER_IMPLEMENTATION = _routerImplementation; + } + + /// @notice Returns the id corresponding to a token + /// @param _token the token address + /// @return _id the token id + function id(IERC20 _token) internal pure returns (uint _id) { + assembly { + _id := _token + } + } + + /// @notice Returns the balance of a token + /// @param _user the user address + /// @param _token the token address + /// @return _balance the token balance + function balanceOf(address _user, IERC20 _token) public view returns (uint) { + return balanceOf(_user, id(_token)); + } + + /// @notice Returns the full balances of a user given a list of tokens + /// * Only non-zero balances are returned + /// @param _user the user address + /// @param _tokens the tokens addresses + /// @return tokens the tokens with non-zero balances + /// @return amounts the non-zero balances + function getFullBalancesParams(address _user, IERC20[] calldata _tokens) + external + view + returns (IERC20[] memory tokens, uint[] memory amounts) + { + uint[] memory _amounts = new uint[](_tokens.length); + uint nTokens; + for (uint i = 0; i < _tokens.length; i++) { + _amounts[i] = balanceOf(_user, _tokens[i]); + if (_amounts[i] > 0) { + nTokens++; + } + } + + tokens = new IERC20[](nTokens); + amounts = new uint[](nTokens); + + uint j; + for (uint i = 0; i < _tokens.length; i++) { + if (_amounts[i] > 0) { + tokens[j] = _tokens[i]; + amounts[j] = _amounts[i]; + j++; + } + } + } + + /// @notice Returns the user router address + /// @param _user the user address + /// @return router the user router address + function userRouter(address _user) public view returns (address) { + return routerProxyFactory.computeProxyAddress(_user, ROUTER_IMPLEMENTATION); + } + + /// @notice Changes the position ID + /// @param _user the user address + /// @param _positionId the position ID + function changePosition(address _user, uint _positionId) external onlyAllowed(_user) { + positions[_user] = _positionId; + emit PositionChanged(_user, _positionId); + } + + /// @notice Retracts the balance of a token + /// @dev No safety checks are performed + /// @param _user the user address + /// @param _token the token retracted from the balance + /// @param _amount the amount retracted from the balance + /// @param _destination the destination address + function _retractBalance(address _user, IERC20 _token, uint _amount, address _destination) internal { + if (_amount == 0) { + return; + } + require(balanceOf(_user, _token) >= _amount, "UniV3Manager/insufficient-balance"); + require(TransferLib.transferToken(_token, _destination, _amount), "UniV3Manager/transfer-failed"); + } + + /// @notice Retracts the balance of a token + /// @param _user the user address + /// @param _token the token retracted from the balance + /// @param _amount the amount retracted from the balance + /// @param _destination the destination address + function _retractBalanceSingle(address _user, IERC20 _token, uint _amount, address _destination) internal { + _retractBalance(_user, _token, _amount, _destination); + _burn(_user, id(_token), _amount); + } + + /// @notice Retracts the balance of a token + /// @param _user the user address + /// @param _tokens the tokens retracted from the balance + /// @param _amounts the amounts retracted from the balance + /// @param _destination the destination address + function _batchRetractBalance( + address _user, + IERC20[] calldata _tokens, + uint[] calldata _amounts, + address _destination + ) internal { + uint[] memory _ids = new uint[](_tokens.length); + for (uint i = 0; i < _tokens.length; i++) { + _ids[i] = id(_tokens[i]); + _retractBalance(_user, _tokens[i], _amounts[i], _destination); + } + _burnBatch(_user, _ids, _amounts); + } + + // -- User balances functions -- + + /// @notice Retracts the balance of a token + /// @param _user the user address + /// @param _token the token retracted from the balance + /// @param _destination the destination address + function retractBalance(address _user, IERC20 _token, address _destination) external onlyAllowed(_user) { + _retractBalanceSingle(_user, _token, balanceOf(_user, _token), _destination); + } + + /// @notice Retracts an amount of a token from the balance + /// @param _user the user address + /// @param _token the token retracted from the balance + /// @param _amount the amount retracted from the balance + /// @param _destination the destination address + function retractAmount(address _user, IERC20 _token, uint _amount, address _destination) external onlyAllowed(_user) { + _retractBalanceSingle(_user, _token, _amount, _destination); + } + + /// @notice Retracts an amount of a token from the balance + /// @param _user the user address + /// @param _tokens the token retracted from the balance + /// @param _amounts the amount retracted from the balance + /// @param _destination the destination address + function retractAmounts(address _user, IERC20[] calldata _tokens, uint[] calldata _amounts, address _destination) + external + onlyAllowed(_user) + { + _batchRetractBalance(_user, _tokens, _amounts, _destination); + } + + // -- router functions -- + + /// @notice Adds to the balance of a token + /// @param _user the user address + /// @param _tokens the tokens added to the balance + /// @param _amounts the amounts added to the balance + function addToBalances(address _user, IERC20[] calldata _tokens, uint[] calldata _amounts) + external + onlyUserRouter(_user) + { + uint toMint; + for (uint i = 0; i < _tokens.length; i++) { + IERC20 _token = _tokens[i]; + uint _amount = _amounts[i]; + if (_amount == 0) { + continue; + } + toMint++; + require(TransferLib.transferTokenFrom(_token, msg.sender, address(this), _amount), "UniV3Manager/transfer-failed"); + } + if (toMint == 0) { + return; + } + uint[] memory ids = new uint[](toMint); + uint[] memory amounts = new uint[](toMint); + uint j; + + for (uint i = 0; i < _tokens.length; i++) { + if (_amounts[i] > 0) { + ids[j] = id(_tokens[i]); + amounts[j] = _amounts[i]; + j++; + } + } + + _mintBatch(_user, ids, amounts, ""); + } + + /// @notice Takes an amount from the balance of a token to a destination + /// @param _user the user address + /// @param _tokens the tokens taken from the balance + /// @param _amounts the amounts taken from the balance + /// @param _destination the destination address + function routerTakeAmounts(address _user, IERC20[] calldata _tokens, uint[] calldata _amounts, address _destination) + external + onlyUserRouter(_user) + { + _batchRetractBalance(_user, _tokens, _amounts, _destination); + } +} diff --git a/src/strategies/routing_logic/restaking/uni-v3/UniswapV3RoutingLogic.sol b/src/strategies/routing_logic/restaking/uni-v3/UniswapV3RoutingLogic.sol new file mode 100644 index 000000000..efbd52042 --- /dev/null +++ b/src/strategies/routing_logic/restaking/uni-v3/UniswapV3RoutingLogic.sol @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AbstractRoutingLogic, IERC20} from "../../abstract/AbstractRoutingLogic.sol"; +import {UniswapV3Manager} from "./UniswapV3Manager.sol"; +import {INonfungiblePositionManager} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol"; +import {PoolAddress} from "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/libraries/PoolAddress.sol"; +import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import {TickMath} from "@mgv-strats/src/strategies/vendor/uniswap/v3/core/libraries/TickMath.sol"; +import {LiquidityAmounts} from "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/libraries/LiquidityAmounts.sol"; +import {TransferLib} from "@mgv/lib/TransferLib.sol"; + +/// @title UniswapV3RoutingLogic +/// @author Mangrove DAO +/// @notice This contract is used to manage the routing logic for Uniswap V3 +contract UniswapV3RoutingLogic is AbstractRoutingLogic { + /// @notice The Uniswap V3 manager contract + UniswapV3Manager public immutable manager; + + /// @notice The Uniswap V3 position manager contract + INonfungiblePositionManager public immutable positionManager; + + /// @notice The Uniswap V3 factory address + address public immutable factory; + + /// @notice The Uniswap V3 position struct + struct Position { + uint96 nonce; + address operator; + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint128 liquidity; + uint feeGrowthInside0LastX128; + uint feeGrowthInside1LastX128; + uint128 tokensOwed0; + uint128 tokensOwed1; + } + + /// @notice Construct the Uniswap V3 routing logic + /// @param _manager the Uniswap V3 manager contract + constructor(UniswapV3Manager _manager) { + manager = _manager; + positionManager = _manager.positionManager(); + factory = positionManager.factory(); + } + + /// @notice Get the position from the position ID + /// @param positionId the position ID + /// @return position the position struct + function _positionFromID(uint positionId) internal view returns (Position memory position) { + (bool success, bytes memory data) = + address(positionManager).staticcall(abi.encodeWithSelector(positionManager.positions.selector, positionId)); + require(success, "UniV3RoutingLogic/position-not-found"); + position = abi.decode(data, (Position)); + } + + /// @notice Get the position from the fund owner + /// @param fundOwner the fund owner address + /// @return positionId the position ID + /// @return position the position struct + function _getPosition(address fundOwner) internal view returns (uint positionId, Position memory position) { + positionId = manager.positions(fundOwner); + position = _positionFromID(positionId); + } + + /// @notice Check if the token is one of the position tokens + /// @param token the token + /// @param position the position + function _anyOfToken(IERC20 token, Position memory position) internal pure { + require(token == IERC20(position.token0) || token == IERC20(position.token1), "UniV3RoutingLogic/invalid-token"); + } + + /// @notice Gets the current price from a pool + /// @param token0 The first token + /// @param token1 The second token + /// @param fee The fee + /// @return sqrtPriceX96 The current price + function getCurrentSqrtRatioX96(address token0, address token1, uint24 fee) + internal + view + returns (uint160 sqrtPriceX96) + { + PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey(token0, token1, fee); + IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); + (sqrtPriceX96,,,,,,) = pool.slot0(); + } + + /// @notice Get the amount owed of a token given a position + /// @param _token the token + /// @param position the position + /// @return owed the amount owed + function _owedOf(IERC20 _token, Position memory position) internal pure returns (uint owed) { + address token = address(_token); + if (token == position.token0) { + owed = position.tokensOwed0; + } + if (token == position.token1) { + owed = position.tokensOwed1; + } + } + + /// @notice Get the amount in the manager + /// @param token the token + /// @param fundOwner the fund owner + /// @return inManager the amount in the manager + function _inManager(IERC20 token, address fundOwner) internal view returns (uint) { + return manager.balanceOf(fundOwner, token); + } + + /// @notice Collect the fees from a position (or unused tokens) + /// @param positionId the position ID + /// @return amount0 the amount of token0 + /// @return amount1 the amount of token1 + function _collect(uint positionId) internal returns (uint amount0, uint amount1) { + INonfungiblePositionManager.CollectParams memory params; + params.tokenId = positionId; + params.recipient = address(this); + params.amount0Max = type(uint128).max; + params.amount1Max = type(uint128).max; + (amount0, amount1) = positionManager.collect(params); + } + + /// @notice Get the tokens array + /// @param position the position + /// @return tokens the tokens array + function _getTokensArray(Position memory position) internal pure returns (IERC20[] memory tokens) { + tokens = new IERC20[](2); + tokens[0] = IERC20(position.token0); + tokens[1] = IERC20(position.token1); + } + + /// @notice Take all tokens from the manager + /// @param tokens the tokens + /// @param fundOwner the fund owner + function _takeAllFromManager(IERC20[] memory tokens, address fundOwner) internal { + uint[] memory amounts; + (tokens, amounts) = manager.getFullBalancesParams(fundOwner, tokens); + if (tokens.length == 0) { + return; + } + manager.routerTakeAmounts(fundOwner, tokens, amounts, address(this)); + } + + /// @notice Send all tokens to the manager + /// @param tokens the tokens + /// @param fundOwner the fund owner + function _sendAllToManager(IERC20[] memory tokens, address fundOwner) internal { + uint[] memory amounts = new uint[](tokens.length); + for (uint i = 0; i < tokens.length; i++) { + amounts[i] = tokens[i].balanceOf(address(this)); + if (amounts[i] > 0) { + require(TransferLib.approveToken(tokens[i], address(manager), amounts[i]), "UniV3RoutingLogic/approve-failed"); + } + } + manager.addToBalances(fundOwner, tokens, amounts); + } + + /// @notice Approve the position manager for a given amount of tokens + /// @param token the token to approve + /// @param amount the amount to approve + function _approvePositionManager(IERC20 token, uint amount) internal { + require(TransferLib.approveToken(token, address(positionManager), amount), "UniV3RoutingLogic/approve-failed"); + } + + /// @notice Reposition the position + /// @param positionId the position ID + /// @param token0 the first token + /// @param token1 the second token + function _reposition(uint positionId, IERC20 token0, IERC20 token1) internal { + // check balances of amount 0 and amount 1 + uint amount0Desired = token0.balanceOf(address(this)); + uint amount1Desired = token1.balanceOf(address(this)); + // give allowance to the position manager for these tokens + _approvePositionManager(token0, amount0Desired); + _approvePositionManager(token1, amount1Desired); + // reposition + INonfungiblePositionManager.IncreaseLiquidityParams memory params; + params.tokenId = positionId; + params.amount0Desired = amount0Desired; + params.amount1Desired = amount1Desired; + params.deadline = type(uint).max; + try positionManager.increaseLiquidity(params) {} catch {} + // resetting allowances (ensure no allowance is left behind and gas refund for storage reset) + _approvePositionManager(token0, 0); + _approvePositionManager(token1, 0); + } + + /// @notice Get the amounts in a position + /// @param position the position + /// @return amount0 the amount of token0 + /// @return amount1 the amount of token1 + function _amountsInPosition(Position memory position) internal view returns (uint amount0, uint amount1) { + uint160 sqrtPriceX96 = getCurrentSqrtRatioX96(position.token0, position.token1, position.fee); + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(position.tickLower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(position.tickUpper); + (amount0, amount1) = + LiquidityAmounts.getAmountsForLiquidity(sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96, position.liquidity); + } + + /// @inheritdoc AbstractRoutingLogic + /// @dev the pull logics first checks if it has enough to collect and in manager to send to mangrove and avoid uneceesary decrease in liquidity + /// * if not, we first decrease the full liquidity + /// * In any case, we then collect from the position, remove all tokens from the manager + /// * Then we send the necessary amount to the maker contract + /// * finally we reposition and send the remaining amount to the manager + function pullLogic(IERC20 token, address fundOwner, uint amount, bool) + external + virtual + override + returns (uint pulled) + { + // get the position + (uint positionId, Position memory position) = _getPosition(fundOwner); + // preflight checks to save gas in case of failure + _anyOfToken(token, position); + // remove position if we don't have enough when collecting + if (_owedOf(token, position) + _inManager(token, fundOwner) < amount) { + // remove full position + INonfungiblePositionManager.DecreaseLiquidityParams memory params; + params.tokenId = positionId; + params.liquidity = position.liquidity; + params.deadline = type(uint).max; + positionManager.decreaseLiquidity(params); + // update the position in memory + // position = _positionFromID(positionId); // update not needed given the rest of the function only uses the tokens + } + // collect + _collect(positionId); + // create token array + IERC20[] memory tokens = _getTokensArray(position); + // take all tokens from manager + _takeAllFromManager(tokens, fundOwner); + // send to msg.sender + require(TransferLib.transferToken(token, msg.sender, amount), "UniV3RoutingLogic/pull-failed"); + // try to reposition with the given amounts of token0 and token1 + _reposition(positionId, IERC20(position.token0), IERC20(position.token1)); + // send remaining amount of token0 and token1 to manager + _sendAllToManager(tokens, fundOwner); + // return amount + return amount; + } + + /// @inheritdoc AbstractRoutingLogic + /// @dev the push logics first collect all fees from the position, then take all tokens from the manager + /// * It then repositions the position and sends the remaining amount to the manager + function pushLogic(IERC20 token, address fundOwner, uint amount) external virtual override returns (uint pushed) { + // get the position choosen by the user + (uint positionId, Position memory position) = _getPosition(fundOwner); + // preflight checks to save gas in case of failure + _anyOfToken(token, position); + // push directly to manager (avoid gas vost of trying to reposition) + require(TransferLib.transferTokenFrom(token, msg.sender, address(this), amount), "UniV3RoutingLogic/push-failed"); + // collect all to this + _collect(positionId); + // Get tokens array + IERC20[] memory tokens = _getTokensArray(position); + // take all tokens from manager + _takeAllFromManager(tokens, fundOwner); + // reposition + _reposition(positionId, IERC20(position.token0), IERC20(position.token1)); + // send remaining amount of token0 and token1 to manager + _sendAllToManager(tokens, fundOwner); + // return amount + return amount; + } + + /// @notice Get the balance of a token + /// @param token The token + /// @param fundOwner The fund owner + /// @param position The position + /// @return managerBalance the amount in the manager + /// @return owed the amount owed by the position + /// @return inPosition the amount in the position + function _balanceOf(IERC20 token, address fundOwner, Position memory position) + internal + view + returns (uint managerBalance, uint owed, uint inPosition) + { + // amount in manager + amount owed + amount from liquidity + // amount in manager + managerBalance = manager.balanceOf(fundOwner, token); + // amount owed + owed = _owedOf(token, position); + // amount from liquidity + (uint amount0, uint amount1) = _amountsInPosition(position); + if (address(token) == position.token0) { + inPosition = amount0; + } + if (address(token) == position.token1) { + inPosition = amount1; + } + } + + /// @inheritdoc AbstractRoutingLogic + function balanceLogic(IERC20 token, address fundOwner) external view virtual override returns (uint balance) { + (, Position memory position) = _getPosition(fundOwner); + (uint managerBalance, uint owed, uint inPosition) = _balanceOf(token, fundOwner, position); + balance = managerBalance + owed + inPosition; + } +} diff --git a/src/strategies/vendor/blast/IERC20Rebasing.sol b/src/strategies/vendor/blast/IERC20Rebasing.sol new file mode 100644 index 000000000..f3d87d806 --- /dev/null +++ b/src/strategies/vendor/blast/IERC20Rebasing.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +enum YieldMode { + AUTOMATIC, + VOID, + CLAIMABLE +} + +interface IERC20Rebasing { + // changes the yield mode of the caller and update the balance + // to reflect the configuration + function configure(YieldMode) external returns (uint); + // "claimable" yield mode accounts can call this this claim their yield + // to another address + function claim(address recipient, uint amount) external returns (uint); + // read the claimable amount for an account + function getClaimableAmount(address account) external view returns (uint); +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/IERC20Minimal.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/IERC20Minimal.sol new file mode 100644 index 000000000..c303265a3 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/IERC20Minimal.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Minimal ERC20 interface for Uniswap +/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 +interface IERC20Minimal { + /// @notice Returns the balance of a token + /// @param account The account for which to look up the number of tokens it has, i.e. its balance + /// @return The number of tokens held by the account + function balanceOf(address account) external view returns (uint256); + + /// @notice Transfers the amount of token from the `msg.sender` to the recipient + /// @param recipient The account that will receive the amount transferred + /// @param amount The number of tokens to send from the sender to the recipient + /// @return Returns true for a successful transfer, false for an unsuccessful transfer + function transfer(address recipient, uint256 amount) external returns (bool); + + /// @notice Returns the current allowance given to a spender by an owner + /// @param owner The account of the token owner + /// @param spender The account of the token spender + /// @return The current allowance granted by `owner` to `spender` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` + /// @param spender The account which will be allowed to spend a given amount of the owners tokens + /// @param amount The amount of tokens allowed to be used by `spender` + /// @return Returns true for a successful approval, false for unsuccessful + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` + /// @param sender The account from which the transfer will be initiated + /// @param recipient The recipient of the transfer + /// @param amount The amount of the transfer + /// @return Returns true for a successful transfer, false for unsuccessful + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. + /// @param from The account from which the tokens were sent, i.e. the balance decreased + /// @param to The account to which the tokens were sent, i.e. the balance increased + /// @param value The amount of tokens that were transferred + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. + /// @param owner The account that approved spending of its tokens + /// @param spender The account for which the spending allowance was modified + /// @param value The new allowance from the owner to the spender + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3Factory.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3Factory.sol new file mode 100644 index 000000000..540cfdc68 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3Factory.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title The interface for the Uniswap V3 Factory +/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees +interface IUniswapV3Factory { + /// @notice Emitted when the owner of the factory is changed + /// @param oldOwner The owner before the owner was changed + /// @param newOwner The owner after the owner was changed + event OwnerChanged(address indexed oldOwner, address indexed newOwner); + + /// @notice Emitted when a pool is created + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks + /// @param pool The address of the created pool + event PoolCreated( + address indexed token0, + address indexed token1, + uint24 indexed fee, + int24 tickSpacing, + address pool + ); + + /// @notice Emitted when a new fee amount is enabled for pool creation via the factory + /// @param fee The enabled fee, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee + event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); + + /// @notice Returns the current owner of the factory + /// @dev Can be changed by the current owner via setOwner + /// @return The address of the factory owner + function owner() external view returns (address); + + /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled + /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context + /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee + /// @return The tick spacing + function feeAmountTickSpacing(uint24 fee) external view returns (int24); + + /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The pool address + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool); + + /// @notice Creates a pool for the given two tokens and fee + /// @param tokenA One of the two tokens in the desired pool + /// @param tokenB The other of the two tokens in the desired pool + /// @param fee The desired fee for the pool + /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved + /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments + /// are invalid. + /// @return pool The address of the newly created pool + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) external returns (address pool); + + /// @notice Updates the owner of the factory + /// @dev Must be called by the current owner + /// @param _owner The new owner of the factory + function setOwner(address _owner) external; + + /// @notice Enables a fee amount with the given tickSpacing + /// @dev Fee amounts may never be removed once enabled + /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) + /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount + function enableFeeAmount(uint24 fee, int24 tickSpacing) external; +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3Pool.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3Pool.sol new file mode 100644 index 000000000..56df0500d --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3Pool.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import './pool/IUniswapV3PoolImmutables.sol'; +import './pool/IUniswapV3PoolState.sol'; +import './pool/IUniswapV3PoolDerivedState.sol'; +import './pool/IUniswapV3PoolActions.sol'; +import './pool/IUniswapV3PoolOwnerActions.sol'; +import './pool/IUniswapV3PoolEvents.sol'; + +/// @title The interface for a Uniswap V3 Pool +/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform +/// to the ERC20 specification +/// @dev The pool interface is broken up into many smaller pieces +interface IUniswapV3Pool is + IUniswapV3PoolImmutables, + IUniswapV3PoolState, + IUniswapV3PoolDerivedState, + IUniswapV3PoolActions, + IUniswapV3PoolOwnerActions, + IUniswapV3PoolEvents +{ + +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3PoolDeployer.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3PoolDeployer.sol new file mode 100644 index 000000000..72096c1ff --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3PoolDeployer.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title An interface for a contract that is capable of deploying Uniswap V3 Pools +/// @notice A contract that constructs a pool must implement this to pass arguments to the pool +/// @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash +/// of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain +interface IUniswapV3PoolDeployer { + /// @notice Get the parameters to be used in constructing the pool, set transiently during pool creation. + /// @dev Called by the pool constructor to fetch the parameters of the pool + /// Returns factory The factory address + /// Returns token0 The first token of the pool by address sort order + /// Returns token1 The second token of the pool by address sort order + /// Returns fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// Returns tickSpacing The minimum number of ticks between initialized ticks + function parameters() + external + view + returns ( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ); +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/LICENSE b/src/strategies/vendor/uniswap/v3/core/interfaces/LICENSE new file mode 100644 index 000000000..ecbc05937 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. \ No newline at end of file diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3FlashCallback.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3FlashCallback.sol new file mode 100644 index 000000000..18e54c4e1 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3FlashCallback.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Callback for IUniswapV3PoolActions#flash +/// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface +interface IUniswapV3FlashCallback { + /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash. + /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param fee0 The fee amount in token0 due to the pool by the end of the flash + /// @param fee1 The fee amount in token1 due to the pool by the end of the flash + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3MintCallback.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3MintCallback.sol new file mode 100644 index 000000000..85447e84f --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3MintCallback.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Callback for IUniswapV3PoolActions#mint +/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface +interface IUniswapV3MintCallback { + /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint. + /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity + /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external; +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3SwapCallback.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3SwapCallback.sol new file mode 100644 index 000000000..9f183b22a --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3SwapCallback.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolActions.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolActions.sol new file mode 100644 index 000000000..44fb61c24 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolActions.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Permissionless pool actions +/// @notice Contains pool methods that can be called by anyone +interface IUniswapV3PoolActions { + /// @notice Sets the initial price for the pool + /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 + function initialize(uint160 sqrtPriceX96) external; + + /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position + /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback + /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends + /// on tickLower, tickUpper, the amount of liquidity, and the current price. + /// @param recipient The address for which the liquidity will be created + /// @param tickLower The lower tick of the position in which to add liquidity + /// @param tickUpper The upper tick of the position in which to add liquidity + /// @param amount The amount of liquidity to mint + /// @param data Any data that should be passed through to the callback + /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback + /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Collects tokens owed to a position + /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. + /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or + /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the + /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. + /// @param recipient The address which should receive the fees collected + /// @param tickLower The lower tick of the position for which to collect fees + /// @param tickUpper The upper tick of the position for which to collect fees + /// @param amount0Requested How much token0 should be withdrawn from the fees owed + /// @param amount1Requested How much token1 should be withdrawn from the fees owed + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); + + /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position + /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 + /// @dev Fees must be collected separately via a call to #collect + /// @param tickLower The lower tick of the position for which to burn liquidity + /// @param tickUpper The upper tick of the position for which to burn liquidity + /// @param amount How much liquidity to burn + /// @return amount0 The amount of token0 sent to the recipient + /// @return amount1 The amount of token1 sent to the recipient + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback + /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback + /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling + /// with 0 amount{0,1} and sending the donation amount(s) from the callback + /// @param recipient The address which will receive the token0 and token1 amounts + /// @param amount0 The amount of token0 to send + /// @param amount1 The amount of token1 to send + /// @param data Any data to be passed through to the callback + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external; + + /// @notice Increase the maximum number of price and liquidity observations that this pool will store + /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to + /// the input observationCardinalityNext. + /// @param observationCardinalityNext The desired minimum number of observations for the pool to store + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolDerivedState.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolDerivedState.sol new file mode 100644 index 000000000..eda3a0089 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolDerivedState.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Pool state that is not stored +/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the +/// blockchain. The functions here may have variable gas costs. +interface IUniswapV3PoolDerivedState { + /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp + /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing + /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, + /// you must call it with secondsAgos = [3600, 0]. + /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in + /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. + /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned + /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp + /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block + /// timestamp + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); + + /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range + /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. + /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first + /// snapshot is taken and the second snapshot is taken. + /// @param tickLower The lower tick of the range + /// @param tickUpper The upper tick of the range + /// @return tickCumulativeInside The snapshot of the tick accumulator for the range + /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range + /// @return secondsInside The snapshot of seconds per liquidity for the range + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ); +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolEvents.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolEvents.sol new file mode 100644 index 000000000..9d915dde9 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolEvents.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Events emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolEvents { + /// @notice Emitted exactly once by a pool when #initialize is first called on the pool + /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize + /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 + /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool + event Initialize(uint160 sqrtPriceX96, int24 tick); + + /// @notice Emitted when liquidity is minted for a given position + /// @param sender The address that minted the liquidity + /// @param owner The owner of the position and recipient of any minted liquidity + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity minted to the position range + /// @param amount0 How much token0 was required for the minted liquidity + /// @param amount1 How much token1 was required for the minted liquidity + event Mint( + address sender, + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted when fees are collected by the owner of a position + /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees + /// @param owner The owner of the position for which fees are collected + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount0 The amount of token0 fees collected + /// @param amount1 The amount of token1 fees collected + event Collect( + address indexed owner, + address recipient, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount0, + uint128 amount1 + ); + + /// @notice Emitted when a position's liquidity is removed + /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect + /// @param owner The owner of the position for which liquidity is removed + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity to remove + /// @param amount0 The amount of token0 withdrawn + /// @param amount1 The amount of token1 withdrawn + event Burn( + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted by the pool for any swaps between token0 and token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the output of the swap + /// @param amount0 The delta of the token0 balance of the pool + /// @param amount1 The delta of the token1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of price of the pool after the swap + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + + /// @notice Emitted by the pool for any flashes of token0/token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the tokens from flash + /// @param amount0 The amount of token0 that was flashed + /// @param amount1 The amount of token1 that was flashed + /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee + /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee + event Flash( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1, + uint256 paid0, + uint256 paid1 + ); + + /// @notice Emitted by the pool for increases to the number of observations that can be stored + /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index + /// just before a mint/swap/burn. + /// @param observationCardinalityNextOld The previous value of the next observation cardinality + /// @param observationCardinalityNextNew The updated value of the next observation cardinality + event IncreaseObservationCardinalityNext( + uint16 observationCardinalityNextOld, + uint16 observationCardinalityNextNew + ); + + /// @notice Emitted when the protocol fee is changed by the pool + /// @param feeProtocol0Old The previous value of the token0 protocol fee + /// @param feeProtocol1Old The previous value of the token1 protocol fee + /// @param feeProtocol0New The updated value of the token0 protocol fee + /// @param feeProtocol1New The updated value of the token1 protocol fee + event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); + + /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner + /// @param sender The address that collects the protocol fees + /// @param recipient The address that receives the collected protocol fees + /// @param amount0 The amount of token0 protocol fees that is withdrawn + /// @param amount0 The amount of token1 protocol fees that is withdrawn + event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolImmutables.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolImmutables.sol new file mode 100644 index 000000000..c9beb151e --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolImmutables.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Pool state that never changes +/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values +interface IUniswapV3PoolImmutables { + /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface + /// @return The contract address + function factory() external view returns (address); + + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address + function token0() external view returns (address); + + /// @notice The second of the two tokens of the pool, sorted by address + /// @return The token contract address + function token1() external view returns (address); + + /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 + /// @return The fee + function fee() external view returns (uint24); + + /// @notice The pool tick spacing + /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive + /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... + /// This value is an int24 to avoid casting even though it is always positive. + /// @return The tick spacing + function tickSpacing() external view returns (int24); + + /// @notice The maximum amount of position liquidity that can use any tick in the range + /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and + /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool + /// @return The max amount of liquidity per tick + function maxLiquidityPerTick() external view returns (uint128); +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolOwnerActions.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolOwnerActions.sol new file mode 100644 index 000000000..2395ed321 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolOwnerActions.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Permissioned pool actions +/// @notice Contains pool methods that may only be called by the factory owner +interface IUniswapV3PoolOwnerActions { + /// @notice Set the denominator of the protocol's % share of the fees + /// @param feeProtocol0 new protocol fee for token0 of the pool + /// @param feeProtocol1 new protocol fee for token1 of the pool + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; + + /// @notice Collect the protocol fee accrued to the pool + /// @param recipient The address to which collected protocol fees should be sent + /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 + /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 + /// @return amount0 The protocol fee collected in token0 + /// @return amount1 The protocol fee collected in token1 + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); +} diff --git a/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolState.sol b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolState.sol new file mode 100644 index 000000000..620256c31 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/interfaces/pool/IUniswapV3PoolState.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Pool state that can change +/// @notice These methods compose the pool's state, and can change with any frequency including multiple times +/// per transaction +interface IUniswapV3PoolState { + /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas + /// when accessed externally. + /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value + /// tick The current tick of the pool, i.e. according to the last tick transition that was run. + /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick + /// boundary. + /// observationIndex The index of the last oracle observation that was written, + /// observationCardinality The current maximum number of observations stored in the pool, + /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// feeProtocol The protocol fee for both tokens of the pool. + /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 + /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. + /// unlocked Whether the pool is currently locked to reentrancy + function slot0() + external + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + + /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal0X128() external view returns (uint256); + + /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal1X128() external view returns (uint256); + + /// @notice The amounts of token0 and token1 that are owed to the protocol + /// @dev Protocol fees will never exceed uint128 max in either token + function protocolFees() external view returns (uint128 token0, uint128 token1); + + /// @notice The currently in range liquidity available to the pool + /// @dev This value has no relationship to the total liquidity across all ticks + function liquidity() external view returns (uint128); + + /// @notice Look up information about a specific tick in the pool + /// @param tick The tick to look up + /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or + /// tick upper, + /// liquidityNet how much liquidity changes when the pool price crosses the tick, + /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// secondsOutside the seconds spent on the other side of the tick from the current tick, + /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. + /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. + /// In addition, these values are only relative and must be used only in comparison to previous snapshots for + /// a specific position. + function ticks(int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); + + /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information + function tickBitmap(int16 wordPosition) external view returns (uint256); + + /// @notice Returns the information about a position by the position's key + /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper + /// @return _liquidity The amount of liquidity in the position, + /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke + function positions(bytes32 key) + external + view + returns ( + uint128 _liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + /// @notice Returns data about a specific observation index + /// @param index The element of the observations array to fetch + /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time + /// ago, rather than at a specific index in the array. + /// @return blockTimestamp The timestamp of the observation, + /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// Returns initialized whether the observation has been initialized and the values are safe to use + function observations(uint256 index) + external + view + returns ( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized + ); +} diff --git a/src/strategies/vendor/uniswap/v3/core/libraries/FullMath.sol b/src/strategies/vendor/uniswap/v3/core/libraries/FullMath.sol new file mode 100644 index 000000000..ca443c2af --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/libraries/FullMath.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Contains 512-bit math functions +/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision +/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits +library FullMath { + error MathOverflowedMulDiv(); + + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param x The multiplicand + /// @param y The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv(uint x, uint y, uint denominator) internal pure returns (uint result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint prod0 = x * y; // Least significant 256 bits of the product + uint prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. + // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. + + uint twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also + // works in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp(uint a, uint b, uint denominator) internal pure returns (uint result) { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint).max); + result++; + } + } +} diff --git a/src/strategies/vendor/uniswap/v3/core/libraries/TickMath.sol b/src/strategies/vendor/uniswap/v3/core/libraries/TickMath.sol new file mode 100644 index 000000000..94093a931 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/core/libraries/TickMath.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/// @title Math library for computing sqrt prices from ticks and vice versa +/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports +/// prices between 2**-128 and 2**128 +library TickMath { + /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + int24 internal constant MAX_TICK = -MIN_TICK; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) + /// at the given tick + function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + uint absTick = tick < 0 ? uint(-int(tick)) : uint(int(tick)); + require(absTick <= uint(uint24(MAX_TICK)), "T"); + + uint ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) ratio = type(uint).max / ratio; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtRatio of the output price is always consistent + sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); + } + + /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may + /// ever return. + /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio + function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, "R"); + uint ratio = uint(sqrtPriceX96) << 32; + + uint r = ratio; + uint msb = 0; + + assembly { + let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(5, gt(r, 0xFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(4, gt(r, 0xFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(3, gt(r, 0xFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(2, gt(r, 0xF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(1, gt(r, 0x3)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := gt(r, 0x1) + msb := or(msb, f) + } + + if (msb >= 128) r = ratio >> (msb - 127); + else r = ratio << (127 - msb); + + int log_2 = (int(msb) - 128) << 64; + + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number + + int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); + int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); + + tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; + } +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IERC20Metadata.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IERC20Metadata.sol new file mode 100644 index 000000000..7ec3ba0dc --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IERC20Metadata.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +/// @title IERC20Metadata +/// @title Interface for ERC20 Metadata +/// @notice Extension to IERC20 that includes token metadata +interface IERC20Metadata is IERC20 { + /// @return The name of the token + function name() external view returns (string memory); + + /// @return The symbol of the token + function symbol() external view returns (string memory); + + /// @return The number of decimal places the token has + function decimals() external view returns (uint8); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IERC721Permit.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IERC721Permit.sol new file mode 100644 index 000000000..744cd3a11 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IERC721Permit.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; + +/// @title ERC721 with permit +/// @notice Extension to ERC721 that includes a permit function for signature based approvals +interface IERC721Permit is IERC721 { + /// @notice The permit typehash used in the permit signature + /// @return The typehash for the permit + function PERMIT_TYPEHASH() external pure returns (bytes32); + + /// @notice The domain separator used in the permit signature + /// @return The domain seperator used in encoding of permit signature + function DOMAIN_SEPARATOR() external view returns (bytes32); + + /// @notice Approve of a specific token ID for spending by spender via signature + /// @param spender The account that is being approved + /// @param tokenId The ID of the token that is being approved for spending + /// @param deadline The deadline timestamp by which the call must be mined for the approve to work + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function permit( + address spender, + uint256 tokenId, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IMulticall.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IMulticall.sol new file mode 100644 index 000000000..1ea39c692 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IMulticall.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + /// @dev The `msg.value` should not be trusted for any method callable from multicall. + /// @param data The encoded function data for each of the calls to make to this contract + /// @return results The results from each of the calls passed in via data + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol new file mode 100644 index 000000000..3d2cbcec1 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol'; +import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol'; + +import './IPoolInitializer.sol'; +import './IERC721Permit.sol'; +import './IPeripheryPayments.sol'; +import './IPeripheryImmutableState.sol'; + +/// @title Non-fungible token for positions +/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred +/// and authorized. +interface INonfungiblePositionManager is + IPoolInitializer, + IPeripheryPayments, + IPeripheryImmutableState, + IERC721Metadata, + IERC721Enumerable, + IERC721Permit +{ + /// @notice Emitted when liquidity is increased for a position NFT + /// @dev Also emitted when a token is minted + /// @param tokenId The ID of the token for which liquidity was increased + /// @param liquidity The amount by which liquidity for the NFT position was increased + /// @param amount0 The amount of token0 that was paid for the increase in liquidity + /// @param amount1 The amount of token1 that was paid for the increase in liquidity + event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + /// @notice Emitted when liquidity is decreased for a position NFT + /// @param tokenId The ID of the token for which liquidity was decreased + /// @param liquidity The amount by which liquidity for the NFT position was decreased + /// @param amount0 The amount of token0 that was accounted for the decrease in liquidity + /// @param amount1 The amount of token1 that was accounted for the decrease in liquidity + event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + /// @notice Emitted when tokens are collected for a position NFT + /// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior + /// @param tokenId The ID of the token for which underlying tokens were collected + /// @param recipient The address of the account that received the collected tokens + /// @param amount0 The amount of token0 owed to the position that was collected + /// @param amount1 The amount of token1 owed to the position that was collected + event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1); + + /// @notice Returns the position information associated with a given token ID. + /// @dev Throws if the token ID is not valid. + /// @param tokenId The ID of the token that represents the position + /// @return nonce The nonce for permits + /// @return operator The address that is approved for spending + /// @return token0 The address of the token0 for a specific pool + /// @return token1 The address of the token1 for a specific pool + /// @return fee The fee associated with the pool + /// @return tickLower The lower end of the tick range for the position + /// @return tickUpper The higher end of the tick range for the position + /// @return liquidity The liquidity of the position + /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position + /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position + /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation + /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation + function positions(uint256 tokenId) + external + view + returns ( + uint96 nonce, + address operator, + address token0, + address token1, + uint24 fee, + int24 tickLower, + int24 tickUpper, + uint128 liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + struct MintParams { + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + address recipient; + uint256 deadline; + } + + /// @notice Creates a new position wrapped in a NFT + /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized + /// a method does not exist, i.e. the pool is assumed to be initialized. + /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata + /// @return tokenId The ID of the token that represents the minted position + /// @return liquidity The amount of liquidity for this position + /// @return amount0 The amount of token0 + /// @return amount1 The amount of token1 + function mint(MintParams calldata params) + external + payable + returns ( + uint256 tokenId, + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ); + + struct IncreaseLiquidityParams { + uint256 tokenId; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + + /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender` + /// @param params tokenId The ID of the token for which liquidity is being increased, + /// amount0Desired The desired amount of token0 to be spent, + /// amount1Desired The desired amount of token1 to be spent, + /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check, + /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check, + /// deadline The time by which the transaction must be included to effect the change + /// @return liquidity The new liquidity amount as a result of the increase + /// @return amount0 The amount of token0 to acheive resulting liquidity + /// @return amount1 The amount of token1 to acheive resulting liquidity + function increaseLiquidity(IncreaseLiquidityParams calldata params) + external + payable + returns ( + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ); + + struct DecreaseLiquidityParams { + uint256 tokenId; + uint128 liquidity; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + + /// @notice Decreases the amount of liquidity in a position and accounts it to the position + /// @param params tokenId The ID of the token for which liquidity is being decreased, + /// amount The amount by which liquidity will be decreased, + /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity, + /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity, + /// deadline The time by which the transaction must be included to effect the change + /// @return amount0 The amount of token0 accounted to the position's tokens owed + /// @return amount1 The amount of token1 accounted to the position's tokens owed + function decreaseLiquidity(DecreaseLiquidityParams calldata params) + external + payable + returns (uint256 amount0, uint256 amount1); + + struct CollectParams { + uint256 tokenId; + address recipient; + uint128 amount0Max; + uint128 amount1Max; + } + + /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient + /// @param params tokenId The ID of the NFT for which tokens are being collected, + /// recipient The account that should receive the tokens, + /// amount0Max The maximum amount of token0 to collect, + /// amount1Max The maximum amount of token1 to collect + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1); + + /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens + /// must be collected first. + /// @param tokenId The ID of the token that is being burned + function burn(uint256 tokenId) external payable; +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungibleTokenPositionDescriptor.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungibleTokenPositionDescriptor.sol new file mode 100644 index 000000000..b3f27b86b --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungibleTokenPositionDescriptor.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import './INonfungiblePositionManager.sol'; + +/// @title Describes position NFT tokens via URI +interface INonfungibleTokenPositionDescriptor { + /// @notice Produces the URI describing a particular token ID for a position manager + /// @dev Note this URI may be a data: URI with the JSON contents directly inlined + /// @param positionManager The position manager for which to describe the token + /// @param tokenId The ID of the token for which to produce a description, which may not be valid + /// @return The URI of the ERC721-compliant metadata + function tokenURI(INonfungiblePositionManager positionManager, uint256 tokenId) + external + view + returns (string memory); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryImmutableState.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryImmutableState.sol new file mode 100644 index 000000000..b3378052d --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryImmutableState.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Immutable state +/// @notice Functions that return immutable state of the router +interface IPeripheryImmutableState { + /// @return Returns the address of the Uniswap V3 factory + function factory() external view returns (address); + + /// @return Returns the address of WETH9 + function WETH9() external view returns (address); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryPayments.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryPayments.sol new file mode 100644 index 000000000..ac74f8c9e --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryPayments.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +/// @title Periphery Payments +/// @notice Functions to ease deposits and withdrawals of ETH +interface IPeripheryPayments { + /// @notice Unwraps the contract's WETH9 balance and sends it to recipient as ETH. + /// @dev The amountMinimum parameter prevents malicious contracts from stealing WETH9 from users. + /// @param amountMinimum The minimum amount of WETH9 to unwrap + /// @param recipient The address receiving ETH + function unwrapWETH9(uint256 amountMinimum, address recipient) external payable; + + /// @notice Refunds any ETH balance held by this contract to the `msg.sender` + /// @dev Useful for bundling with mint or increase liquidity that uses ether, or exact output swaps + /// that use ether for the input amount + function refundETH() external payable; + + /// @notice Transfers the full amount of a token held by this contract to recipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users + /// @param token The contract address of the token which will be transferred to `recipient` + /// @param amountMinimum The minimum amount of token required for a transfer + /// @param recipient The destination address of the token + function sweepToken( + address token, + uint256 amountMinimum, + address recipient + ) external payable; +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryPaymentsWithFee.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryPaymentsWithFee.sol new file mode 100644 index 000000000..907e44675 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPeripheryPaymentsWithFee.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +import './IPeripheryPayments.sol'; + +/// @title Periphery Payments +/// @notice Functions to ease deposits and withdrawals of ETH +interface IPeripheryPaymentsWithFee is IPeripheryPayments { + /// @notice Unwraps the contract's WETH9 balance and sends it to recipient as ETH, with a percentage between + /// 0 (exclusive), and 1 (inclusive) going to feeRecipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing WETH9 from users. + function unwrapWETH9WithFee( + uint256 amountMinimum, + address recipient, + uint256 feeBips, + address feeRecipient + ) external payable; + + /// @notice Transfers the full amount of a token held by this contract to recipient, with a percentage between + /// 0 (exclusive) and 1 (inclusive) going to feeRecipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users + function sweepTokenWithFee( + address token, + uint256 amountMinimum, + address recipient, + uint256 feeBips, + address feeRecipient + ) external payable; +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPoolInitializer.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPoolInitializer.sol new file mode 100644 index 000000000..d2949b3d6 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IPoolInitializer.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Creates and initializes V3 Pools +/// @notice Provides a method for creating and initializing a pool, if necessary, for bundling with other methods that +/// require the pool to exist. +interface IPoolInitializer { + /// @notice Creates a new pool if it does not exist, then initializes if not initialized + /// @dev This method can be bundled with others via IMulticall for the first action (e.g. mint) performed against a pool + /// @param token0 The contract address of token0 of the pool + /// @param token1 The contract address of token1 of the pool + /// @param fee The fee amount of the v3 pool for the specified token pair + /// @param sqrtPriceX96 The initial square root price of the pool as a Q64.96 value + /// @return pool Returns the pool address based on the pair of tokens and fee, will return the newly created pool address if necessary + function createAndInitializePoolIfNecessary( + address token0, + address token1, + uint24 fee, + uint160 sqrtPriceX96 + ) external payable returns (address pool); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IQuoter.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IQuoter.sol new file mode 100644 index 000000000..3410e0cd0 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IQuoter.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Quoter Interface +/// @notice Supports quoting the calculated amounts from exact input or exact output swaps +/// @dev These functions are not marked view because they rely on calling non-view functions and reverting +/// to compute the result. They are also not gas efficient and should not be called on-chain. +interface IQuoter { + /// @notice Returns the amount out received for a given exact input swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee + /// @param amountIn The amount of the first token to swap + /// @return amountOut The amount of the last token that would be received + function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut); + + /// @notice Returns the amount out received for a given exact input but for a swap of a single pool + /// @param tokenIn The token being swapped in + /// @param tokenOut The token being swapped out + /// @param fee The fee of the token pool to consider for the pair + /// @param amountIn The desired input amount + /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountOut The amount of `tokenOut` that would be received + function quoteExactInputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountIn, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountOut); + + /// @notice Returns the amount in required for a given exact output swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order + /// @param amountOut The amount of the last token to receive + /// @return amountIn The amount of first token required to be paid + function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn); + + /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool + /// @param tokenIn The token being swapped in + /// @param tokenOut The token being swapped out + /// @param fee The fee of the token pool to consider for the pair + /// @param amountOut The desired output amount + /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountIn The amount required as the input for the swap in order to receive `amountOut` + function quoteExactOutputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountOut, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountIn); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IQuoterV2.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IQuoterV2.sol new file mode 100644 index 000000000..3c2961b2c --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IQuoterV2.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title QuoterV2 Interface +/// @notice Supports quoting the calculated amounts from exact input or exact output swaps. +/// @notice For each pool also tells you the number of initialized ticks crossed and the sqrt price of the pool after the swap. +/// @dev These functions are not marked view because they rely on calling non-view functions and reverting +/// to compute the result. They are also not gas efficient and should not be called on-chain. +interface IQuoterV2 { + /// @notice Returns the amount out received for a given exact input swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee + /// @param amountIn The amount of the first token to swap + /// @return amountOut The amount of the last token that would be received + /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + /// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactInput(bytes memory path, uint256 amountIn) + external + returns ( + uint256 amountOut, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); + + struct QuoteExactInputSingleParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + /// @notice Returns the amount out received for a given exact input but for a swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactInputSingleParams` + /// tokenIn The token being swapped in + /// tokenOut The token being swapped out + /// fee The fee of the token pool to consider for the pair + /// amountIn The desired input amount + /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountOut The amount of `tokenOut` that would be received + /// @return sqrtPriceX96After The sqrt price of the pool after the swap + /// @return initializedTicksCrossed The number of initialized ticks that the swap crossed + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactInputSingle(QuoteExactInputSingleParams memory params) + external + returns ( + uint256 amountOut, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ); + + /// @notice Returns the amount in required for a given exact output swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order + /// @param amountOut The amount of the last token to receive + /// @return amountIn The amount of first token required to be paid + /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + /// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactOutput(bytes memory path, uint256 amountOut) + external + returns ( + uint256 amountIn, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); + + struct QuoteExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint256 amount; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams` + /// tokenIn The token being swapped in + /// tokenOut The token being swapped out + /// fee The fee of the token pool to consider for the pair + /// amountOut The desired output amount + /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountIn The amount required as the input for the swap in order to receive `amountOut` + /// @return sqrtPriceX96After The sqrt price of the pool after the swap + /// @return initializedTicksCrossed The number of initialized ticks that the swap crossed + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) + external + returns ( + uint256 amountIn, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/ISelfPermit.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/ISelfPermit.sol new file mode 100644 index 000000000..0ab8a25af --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/ISelfPermit.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermit( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// Can be used instead of #selfPermit to prevent calls from failing due to a frontrun of a call to #selfPermit + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitIfNecessary( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowed( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// Can be used instead of #selfPermitAllowed to prevent calls from failing due to a frontrun of a call to #selfPermitAllowed. + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowedIfNecessary( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/ISwapRouter.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/ISwapRouter.sol new file mode 100644 index 000000000..6124b4360 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/ISwapRouter.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +import '@mgv-strats/src/strategies/vendor/uniswap/v3/core/interfaces/callback/IUniswapV3SwapCallback.sol'; + +/// @title Router token swapping functionality +/// @notice Functions for swapping tokens via Uniswap V3 +interface ISwapRouter is IUniswapV3SwapCallback { + struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 deadline; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another token + /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata + /// @return amountOut The amount of the received token + function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); + + struct ExactInputParams { + bytes path; + address recipient; + uint256 deadline; + uint256 amountIn; + uint256 amountOutMinimum; + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); + + struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 deadline; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; + } + + /// @notice Swaps as little as possible of one token for `amountOut` of another token + /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata + /// @return amountIn The amount of the input token + function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); + + struct ExactOutputParams { + bytes path; + address recipient; + uint256 deadline; + uint256 amountOut; + uint256 amountInMaximum; + } + + /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/ITickLens.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/ITickLens.sol new file mode 100644 index 000000000..89bac2d41 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/ITickLens.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Tick Lens +/// @notice Provides functions for fetching chunks of tick data for a pool +/// @dev This avoids the waterfall of fetching the tick bitmap, parsing the bitmap to know which ticks to fetch, and +/// then sending additional multicalls to fetch the tick data +interface ITickLens { + struct PopulatedTick { + int24 tick; + int128 liquidityNet; + uint128 liquidityGross; + } + + /// @notice Get all the tick data for the populated ticks from a word of the tick bitmap of a pool + /// @param pool The address of the pool for which to fetch populated tick data + /// @param tickBitmapIndex The index of the word in the tick bitmap for which to parse the bitmap and + /// fetch all the populated ticks + /// @return populatedTicks An array of tick data for the given word in the tick bitmap + function getPopulatedTicksInWord(address pool, int16 tickBitmapIndex) + external + view + returns (PopulatedTick[] memory populatedTicks); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/IV3Migrator.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IV3Migrator.sol new file mode 100644 index 000000000..6eb6e42fa --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/IV3Migrator.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +import './IMulticall.sol'; +import './ISelfPermit.sol'; +import './IPoolInitializer.sol'; + +/// @title V3 Migrator +/// @notice Enables migration of liqudity from Uniswap v2-compatible pairs into Uniswap v3 pools +interface IV3Migrator is IMulticall, ISelfPermit, IPoolInitializer { + struct MigrateParams { + address pair; // the Uniswap v2-compatible pair + uint256 liquidityToMigrate; // expected to be balanceOf(msg.sender) + uint8 percentageToMigrate; // represented as a numerator over 100 + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint256 amount0Min; // must be discounted by percentageToMigrate + uint256 amount1Min; // must be discounted by percentageToMigrate + address recipient; + uint256 deadline; + bool refundAsETH; + } + + /// @notice Migrates liquidity to v3 by burning v2 liquidity and minting a new position for v3 + /// @dev Slippage protection is enforced via `amount{0,1}Min`, which should be a discount of the expected values of + /// the maximum amount of v3 liquidity that the v2 liquidity can get. For the special case of migrating to an + /// out-of-range position, `amount{0,1}Min` may be set to 0, enforcing that the position remains out of range + /// @param params The params necessary to migrate v2 liquidity, encoded as `MigrateParams` in calldata + function migrate(MigrateParams calldata params) external; +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IERC1271.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IERC1271.sol new file mode 100644 index 000000000..824b03ea4 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IERC1271.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Interface for verifying contract-based account signatures +/// @notice Interface that verifies provided signature for the data +/// @dev Interface defined by EIP-1271 +interface IERC1271 { + /// @notice Returns whether the provided signature is valid for the provided data + /// @dev MUST return the bytes4 magic value 0x1626ba7e when function passes. + /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5). + /// MUST allow external calls. + /// @param hash Hash of the data to be signed + /// @param signature Signature byte array associated with _data + /// @return magicValue The bytes4 magic value 0x1626ba7e + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IERC20PermitAllowed.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IERC20PermitAllowed.sol new file mode 100644 index 000000000..a817e1cff --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IERC20PermitAllowed.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Interface for permit +/// @notice Interface used by DAI/CHAI for permit +interface IERC20PermitAllowed { + /// @notice Approve the spender to spend some tokens via the holder signature + /// @dev This is the permit interface used by DAI and CHAI + /// @param holder The address of the token holder, the token owner + /// @param spender The address of the token spender + /// @param nonce The holder's nonce, increases at each call to permit + /// @param expiry The timestamp at which the permit is no longer valid + /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function permit( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IWETH9.sol b/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IWETH9.sol new file mode 100644 index 000000000..98338a395 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IWETH9.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +/// @title Interface for WETH9 +interface IWETH9 is IERC20 { + /// @notice Deposit ether to get wrapped ether + function deposit() external payable; + + /// @notice Withdraw wrapped ether to get ether + function withdraw(uint256) external; +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/libraries/LiquidityAmounts.sol b/src/strategies/vendor/uniswap/v3/periphery/libraries/LiquidityAmounts.sol new file mode 100644 index 000000000..93f37ed6b --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/libraries/LiquidityAmounts.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import '../../core/libraries/FullMath.sol'; +import '@uniswap/v3-core/contracts/libraries/FixedPoint96.sol'; + +/// @title Liquidity amount functions +/// @notice Provides functions for computing liquidity amounts from token amounts and prices +library LiquidityAmounts { + /// @notice Downcasts uint256 to uint128 + /// @param x The uint258 to be downcasted + /// @return y The passed value, downcasted to uint128 + function toUint128(uint256 x) private pure returns (uint128 y) { + require((y = uint128(x)) == x); + } + + /// @notice Computes the amount of liquidity received for a given amount of token0 and price range + /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param amount0 The amount0 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount0( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount0 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96); + return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96)); + } + + /// @notice Computes the amount of liquidity received for a given amount of token1 and price range + /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)). + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param amount1 The amount1 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount1( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96)); + } + + /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current + /// pool prices and the prices at the tick boundaries + /// @param sqrtRatioX96 A sqrt price representing the current pool prices + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param amount0 The amount of token0 being sent in + /// @param amount1 The amount of token1 being sent in + /// @return liquidity The maximum amount of liquidity received + function getLiquidityForAmounts( + uint160 sqrtRatioX96, + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount0, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + if (sqrtRatioX96 <= sqrtRatioAX96) { + liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0); + } else if (sqrtRatioX96 < sqrtRatioBX96) { + uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0); + uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1); + + liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1; + } else { + liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1); + } + } + + /// @notice Computes the amount of token0 for a given amount of liquidity and a price range + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param liquidity The liquidity being valued + /// @return amount0 The amount of token0 + function getAmount0ForLiquidity( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount0) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + FullMath.mulDiv( + uint256(liquidity) << FixedPoint96.RESOLUTION, + sqrtRatioBX96 - sqrtRatioAX96, + sqrtRatioBX96 + ) / sqrtRatioAX96; + } + + /// @notice Computes the amount of token1 for a given amount of liquidity and a price range + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param liquidity The liquidity being valued + /// @return amount1 The amount of token1 + function getAmount1ForLiquidity( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + + /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current + /// pool prices and the prices at the tick boundaries + /// @param sqrtRatioX96 A sqrt price representing the current pool prices + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param liquidity The liquidity being valued + /// @return amount0 The amount of token0 + /// @return amount1 The amount of token1 + function getAmountsForLiquidity( + uint160 sqrtRatioX96, + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount0, uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + if (sqrtRatioX96 <= sqrtRatioAX96) { + amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); + } else if (sqrtRatioX96 < sqrtRatioBX96) { + amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity); + amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity); + } else { + amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); + } + } +} diff --git a/src/strategies/vendor/uniswap/v3/periphery/libraries/PoolAddress.sol b/src/strategies/vendor/uniswap/v3/periphery/libraries/PoolAddress.sol new file mode 100644 index 000000000..80c4396f2 --- /dev/null +++ b/src/strategies/vendor/uniswap/v3/periphery/libraries/PoolAddress.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee +library PoolAddress { + bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; + + /// @notice The identifying key of the pool + struct PoolKey { + address token0; + address token1; + uint24 fee; + } + + /// @notice Returns PoolKey: the ordered tokens with the matched fee levels + /// @param tokenA The first token of a pool, unsorted + /// @param tokenB The second token of a pool, unsorted + /// @param fee The fee level of the pool + /// @return Poolkey The pool details with ordered token0 and token1 assignments + function getPoolKey(address tokenA, address tokenB, uint24 fee) internal pure returns (PoolKey memory) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); + } + + /// @notice Deterministically computes the pool address given the factory and PoolKey + /// @param factory The Uniswap V3 factory contract address + /// @param key The PoolKey + /// @return pool The contract address of the V3 pool + function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) { + require(key.token0 < key.token1); + pool = address( + uint160( + uint( + keccak256( + abi.encodePacked( + hex"ff", factory, keccak256(abi.encode(key.token0, key.token1, key.fee)), POOL_INIT_CODE_HASH + ) + ) + ) + ) + ); + } +} diff --git a/src/toy_strategies/utils/ContractDeployer.sol b/src/toy_strategies/utils/ContractDeployer.sol new file mode 100644 index 000000000..362ee2732 --- /dev/null +++ b/src/toy_strategies/utils/ContractDeployer.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +library ContractDeployer { + function deployFromBytecode(bytes memory bytecode) public returns (address result) { + assembly { + let size := mload(bytecode) + let data := add(bytecode, 0x20) + result := create(0, data, size) + if iszero(result) { revert(0, 0) } + } + } + + function deployBytecodeWithArgs(bytes memory bytecode, bytes memory args) public returns (address result) { + bytes memory data = abi.encodePacked(bytecode, args); + result = deployFromBytecode(data); + } +} diff --git a/src/toy_strategies/utils/Univ3Deployer.sol b/src/toy_strategies/utils/Univ3Deployer.sol new file mode 100644 index 000000000..f98d4a311 --- /dev/null +++ b/src/toy_strategies/utils/Univ3Deployer.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test2, toFixed, Test, console, toString, vm} from "@mgv/lib/Test2.sol"; +import {IERC20} from "@mgv/lib/IERC20.sol"; +import {ContractDeployer} from "@mgv-strats/src/toy_strategies/utils/ContractDeployer.sol"; +import {RouterProxy, AbstractRouter} from "@mgv-strats/src/strategies/routers/RouterProxy.sol"; +import {IUniswapV3Factory} from "@mgv-strats/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3Factory.sol"; +import {IWETH9} from "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IWETH9.sol"; +import {INonfungibleTokenPositionDescriptor} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungibleTokenPositionDescriptor.sol"; +import {INonfungiblePositionManager} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol"; + +contract Univ3Deployer { + IUniswapV3Factory public factory; + IWETH9 public weth9; + INonfungibleTokenPositionDescriptor public tokenDescriptor; + INonfungiblePositionManager public positionManager; + + function deployFactory() private { + string memory content = vm.readFile("uni-out/UniswapV3Factory.txt"); + bytes memory bytecode = vm.parseBytes(content); + factory = IUniswapV3Factory(ContractDeployer.deployFromBytecode(bytecode)); + } + + function deployeWETH9() private { + // TODO + } + + function deployTokenDescriptor() private { + string memory content = vm.readFile("uni-out/NonfungibleTokenPositionDescriptor.txt"); + bytes memory bytecode = vm.parseBytes(content); + bytes32 nativeCurrencyLabel = "ETH"; + bytes memory args = abi.encode(address(weth9), nativeCurrencyLabel); + tokenDescriptor = INonfungibleTokenPositionDescriptor(ContractDeployer.deployBytecodeWithArgs(bytecode, args)); + } + + function deployPositionManager() private { + string memory content = vm.readFile("uni-out/NonfungiblePositionManager.txt"); + bytes memory bytecode = vm.parseBytes(content); + bytes memory args = abi.encode(address(factory), address(weth9), address(tokenDescriptor)); + positionManager = INonfungiblePositionManager(ContractDeployer.deployBytecodeWithArgs(bytecode, args)); + } + + function deployUniv3() public { + deployFactory(); + deployeWETH9(); + deployTokenDescriptor(); + deployPositionManager(); + } +} diff --git a/test/lib/UniV3Test.t.sol b/test/lib/UniV3Test.t.sol new file mode 100644 index 000000000..ef2b7ea52 --- /dev/null +++ b/test/lib/UniV3Test.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test2, toFixed, Test, console, toString, vm} from "@mgv/lib/Test2.sol"; +import {IERC20} from "@mgv/lib/IERC20.sol"; +import {ContractDeployer} from "@mgv-strats/src/toy_strategies/utils/ContractDeployer.sol"; +import {RouterProxy, AbstractRouter} from "@mgv-strats/src/strategies/routers/RouterProxy.sol"; +import {IUniswapV3Factory} from "@mgv-strats/src/strategies/vendor/uniswap/v3/core/interfaces/IUniswapV3Factory.sol"; +import {IWETH9} from "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/external/IWETH9.sol"; +import {INonfungibleTokenPositionDescriptor} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungibleTokenPositionDescriptor.sol"; +import {INonfungiblePositionManager} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol"; +import {Univ3Deployer} from "@mgv-strats/src/toy_strategies/utils/Univ3Deployer.sol"; + +contract Univ3Test is Test2, Univ3Deployer { + function setUp() public { + deployUniv3(); + } + + function test_deployFactory() public { + console.log("factory", address(factory)); + } +} diff --git a/test/script/strategies/routing_logic/deployers/UniV3RoutingLogicDeployer.t.sol b/test/script/strategies/routing_logic/deployers/UniV3RoutingLogicDeployer.t.sol new file mode 100644 index 000000000..e4307d6b2 --- /dev/null +++ b/test/script/strategies/routing_logic/deployers/UniV3RoutingLogicDeployer.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import {Deployer} from "@mgv/script/lib/Deployer.sol"; +import {MangroveDeployer} from "@mgv/script/core/deployers/MangroveDeployer.s.sol"; + +import { + UniV3RoutingLogicDeployer, + UniswapV3RoutingLogic, + UniswapV3Manager, + INonfungiblePositionManager, + RouterProxyFactory, + AbstractRouter +} from "@mgv-strats/script/strategies/routing_logic/deployers/uni-v3/UniV3RoutingLogicDeployer.s.sol"; +import {IERC20} from "@mgv/lib/IERC20.sol"; +import {TestToken} from "@mgv/test/lib/tokens/TestToken.sol"; +import {Univ3Deployer} from "@mgv-strats/src/toy_strategies/utils/Univ3Deployer.sol"; +import {Test2} from "@mgv/lib/Test2.sol"; +import {IMangrove} from "@mgv/src/IMangrove.sol"; +import {RouterProxyFactoryDeployer} from + "@mgv-strats/script/strategies/routerProxyFactory/deployers/RouterProxyFactoryDeployer.s.sol"; +import { + MangroveOrderDeployer, + MangroveOrder +} from "@mgv-strats/script/strategies/mangroveOrder/deployers/MangroveOrderDeployer.s.sol"; + +contract Univ3LogicDeployerTest is Deployer, Test2, Univ3Deployer { + UniV3RoutingLogicDeployer salDeployer; + address chief; + + function setUp() public { + chief = freshAddress("admin"); + + deployUniv3(); + + address gasbot = freshAddress("gasbot"); + uint gasprice = 42; + uint gasmax = 8_000_000; + (new MangroveDeployer()).innerRun(chief, gasprice, gasmax, gasbot); + (new RouterProxyFactoryDeployer()).innerRun(); + (new MangroveOrderDeployer()).innerRun( + IMangrove(payable(fork.get("Mangrove"))), chief, RouterProxyFactory(fork.get("RouterProxyFactory")) + ); + salDeployer = new UniV3RoutingLogicDeployer(); + } + + function test_normal_deploy() public { + salDeployer.innerRun({ + positionManager: positionManager, + factory: RouterProxyFactory(fork.get("RouterProxyFactory")), + implementation: AbstractRouter(fork.get("MangroveOrder-Router")), + forkName: "MyFork" + }); + } +} diff --git a/test/strategies/kandel/AaveKandel.t.sol b/test/strategies/kandel/AaveKandel.t.sol index f96daf750..9265afc29 100644 --- a/test/strategies/kandel/AaveKandel.t.sol +++ b/test/strategies/kandel/AaveKandel.t.sol @@ -66,11 +66,7 @@ contract AaveKandelTest is CoreKandelTest { mgv: IMangrove($(mgv)), olKeyBaseQuote: olKey, gasreq: kandel_gasreq, - routerParams: Direct.RouterParams({ - routerImplementation: router, - fundOwner: id, - strict: strict - }) + routerParams: Direct.RouterParams({routerImplementation: router, fundOwner: id, strict: strict}) }); router.bind(address(aaveKandel_)); @@ -372,11 +368,7 @@ contract AaveKandelTest is CoreKandelTest { mgv: IMangrove($(mgv)), gasreq: 700_000, olKeyBaseQuote: OLKey(address(aToken), address(quote), 1), - routerParams: Direct.RouterParams({ - routerImplementation: router, - fundOwner: address(0), - strict: false - }) + routerParams: Direct.RouterParams({routerImplementation: router, fundOwner: address(0), strict: false}) }); } @@ -388,11 +380,7 @@ contract AaveKandelTest is CoreKandelTest { mgv: IMangrove($(mgv)), gasreq: 700_000, olKeyBaseQuote: OLKey(address(base), address(aToken), 1), - routerParams: Direct.RouterParams({ - routerImplementation: router, - fundOwner: address(0), - strict: false - }) + routerParams: Direct.RouterParams({routerImplementation: router, fundOwner: address(0), strict: false}) }); } } diff --git a/test/strategies/kandel/Kandel.t.sol b/test/strategies/kandel/Kandel.t.sol index adb59677e..70df65a4d 100644 --- a/test/strategies/kandel/Kandel.t.sol +++ b/test/strategies/kandel/Kandel.t.sol @@ -23,11 +23,7 @@ contract NoRouterKandelTest is CoreKandelTest { vm.expectEmit(true, true, true, true); emit SetGasreq(GASREQ); vm.startPrank(deployer); - kdl_ = new Kandel({ - mgv: mgv, - olKeyBaseQuote: olKey, - gasreq: GASREQ - }); + kdl_ = new Kandel({mgv: mgv, olKeyBaseQuote: olKey, gasreq: GASREQ}); // activates Kandel for base and quote kdl_.approve(base, $(mgv), type(uint).max); kdl_.approve(quote, $(mgv), type(uint).max); diff --git a/test/strategies/kandel/abstract/KandelTest.t.sol b/test/strategies/kandel/abstract/KandelTest.t.sol index a1ea82719..8db4d0c71 100644 --- a/test/strategies/kandel/abstract/KandelTest.t.sol +++ b/test/strategies/kandel/abstract/KandelTest.t.sol @@ -201,6 +201,7 @@ abstract contract KandelTest is StratTest { Bid, // live bid Ask, // live ask Crossed // both live + } struct IndexStatus { diff --git a/test/strategies/router_logic/Univ3.t.sol b/test/strategies/router_logic/Univ3.t.sol new file mode 100644 index 000000000..ff4c31a8d --- /dev/null +++ b/test/strategies/router_logic/Univ3.t.sol @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +import {StratTest, MgvReader, TestMaker, TestTaker, TestSender, console} from "@mgv-strats/test/lib/StratTest.sol"; +import {TestToken} from "@mgv/test/lib/tokens/TestToken.sol"; +import {MgvLib, IERC20, OLKey, Offer, OfferDetail} from "@mgv/src/core/MgvLib.sol"; +import {AbstractRoutingLogic} from "@mgv-strats/src/strategies/routing_logic/abstract/AbstractRoutingLogic.sol"; +import {Univ3Deployer} from "@mgv-strats/src/toy_strategies/utils/Univ3Deployer.sol"; +import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import {INonfungiblePositionManager} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol"; +import {TickMath} from "@mgv-strats/src/strategies/vendor/uniswap/v3/core/libraries/TickMath.sol"; +import {UniswapV3Manager} from "@mgv-strats/src/strategies/routing_logic/restaking/uni-v3/UniswapV3Manager.sol"; +import {UniswapV3RoutingLogic} from + "@mgv-strats/src/strategies/routing_logic/restaking/uni-v3/UniswapV3RoutingLogic.sol"; + +import {RouterProxyFactory, RouterProxy} from "@mgv-strats/src/strategies/routers/RouterProxyFactory.sol"; +import {SmartRouter, RL} from "@mgv-strats/src/strategies/routers/SmartRouter.sol"; +import {IERC20} from "@mgv/lib/IERC20.sol"; + +contract UniV3_Test is StratTest, Univ3Deployer { + TestToken public token0; + TestToken public token1; + TestToken public token2; + + RouterProxyFactory public proxyFactory; + SmartRouter public routerImplementation; + SmartRouter public router; + + UniswapV3Manager public manager; + UniswapV3RoutingLogic public routingLogic; + + IUniswapV3Pool public pool; + + address public user = 0xC249dBb976015D65CB5F2a8a008bD590Bb9efcd2; + + uint positionId; + + struct Position { + uint96 nonce; + address operator; + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint128 liquidity; + uint feeGrowthInside0LastX128; + uint feeGrowthInside1LastX128; + uint128 tokensOwed0; + uint128 tokensOwed1; + } + + function mintNewPosition() internal returns (uint _positionId) { + INonfungiblePositionManager.MintParams memory params; + params.token0 = address(token0); + params.token1 = address(token1); + params.fee = 500; + params.tickLower = -500; + params.tickUpper = 500; + params.deadline = block.timestamp + 1000; + params.amount0Desired = 1000; + params.amount1Desired = 1000; + params.recipient = user; + + deal($(token0), user, params.amount0Desired); + deal($(token1), user, params.amount1Desired); + + vm.startPrank(user); + token0.approve(address(positionManager), params.amount0Desired); + token1.approve(address(positionManager), params.amount1Desired); + + (_positionId,,,) = positionManager.mint(params); + vm.stopPrank(); + } + + function getRoutingOrder(IERC20 token) internal view returns (RL.RoutingOrder memory order) { + order.fundOwner = user; + order.token = token; + } + + function setLogic(IERC20 token) internal { + RL.RoutingOrder memory order = getRoutingOrder(token); + router.setLogic(order, routingLogic); + } + + function getLiquidity() internal view returns (uint128 liquidity) { + (bool success, bytes memory data) = + address(positionManager).staticcall(abi.encodeWithSelector(positionManager.positions.selector, positionId)); + require(success, "UniV3RoutingLogic/position-not-found"); + Position memory position = abi.decode(data, (Position)); + liquidity = position.liquidity; + } + + function setUp() public override { + deployUniv3(); + + token0 = new TestToken(address(this), "token0", "T0", 18); + token1 = new TestToken(address(this), "token1", "T1", 18); + token2 = new TestToken(address(this), "token2", "T2", 18); + + pool = IUniswapV3Pool(factory.createPool(address(token0), address(token1), 500)); + pool.initialize(TickMath.getSqrtRatioAtTick(0)); + + assertEq(pool.token0(), address(token0)); + assertEq(pool.token1(), address(token1)); + assertEq(pool.fee(), 500); + assertEq(pool.tickSpacing(), 10); + (,,,,,, bool unlocked) = pool.slot0(); + assertTrue(unlocked); + + proxyFactory = new RouterProxyFactory(); + // automatically binds to this + routerImplementation = new SmartRouter(address(this)); + (RouterProxy proxy,) = proxyFactory.instantiate(user, routerImplementation); + router = SmartRouter(address(proxy)); + + manager = new UniswapV3Manager(positionManager, proxyFactory, routerImplementation); + routingLogic = new UniswapV3RoutingLogic(manager); + + positionId = mintNewPosition(); + + vm.startPrank(user); + positionManager.approve(address(router), positionId); + manager.changePosition(user, positionId); + setLogic(token0); + setLogic(token1); + vm.stopPrank(); + } + + function test_push() public { + uint managerBalanceToken0 = manager.balanceOf(user, token0); + uint managerBalanceToken1 = manager.balanceOf(user, token1); + + uint128 liquidity = getLiquidity(); + + assertEq(managerBalanceToken0, 0); + assertEq(managerBalanceToken1, 0); + + uint amount = 1000; + deal($(token0), address(this), amount); + token0.approve(address(router), amount); + RL.RoutingOrder memory order = getRoutingOrder(token0); + router.push(order, amount); + + uint managerBalanceToken0Step2 = manager.balanceOf(user, token0); + uint managerBalanceToken1Step2 = manager.balanceOf(user, token1); + uint128 liquidityStep2 = getLiquidity(); + + assertEq(managerBalanceToken0Step2, amount); + assertEq(managerBalanceToken1Step2, 0); + assertEq(liquidityStep2, liquidity); + + deal($(token1), address(this), amount); + token1.approve(address(router), amount); + order = getRoutingOrder(token1); + router.push(order, amount); + + uint managerBalanceToken0Step3 = manager.balanceOf(user, token0); + uint managerBalanceToken1Step3 = manager.balanceOf(user, token1); + uint128 liquidityStep3 = getLiquidity(); + + assertEq(managerBalanceToken0Step3, 0); + assertEq(managerBalanceToken1Step3, 0); + assertEq(liquidityStep3, liquidity * 2); + } + + function test_pull() public { + uint managerBalanceToken0 = manager.balanceOf(user, token0); + uint managerBalanceToken1 = manager.balanceOf(user, token1); + + uint128 liquidity = getLiquidity(); + + assertEq(managerBalanceToken0, 0); + assertEq(managerBalanceToken1, 0); + + uint amount = 500; + RL.RoutingOrder memory order = getRoutingOrder(token0); + router.pull(order, amount, true); + + uint managerBalanceToken0Step2 = manager.balanceOf(user, token0); + uint managerBalanceToken1Step2 = manager.balanceOf(user, token1); + uint128 liquidityStep2 = getLiquidity(); + + assertEq(managerBalanceToken1Step2, amount); + assertEq(managerBalanceToken0Step2, 0); + assertApproxEqAbs(liquidityStep2, liquidity / 2, 100); + + order = getRoutingOrder(token1); + router.pull(order, amount, true); + + uint managerBalanceToken0Step3 = manager.balanceOf(user, token0); + uint managerBalanceToken1Step3 = manager.balanceOf(user, token1); + uint128 liquidityStep3 = getLiquidity(); + + assertEq(managerBalanceToken0Step3, 0); + assertEq(managerBalanceToken1Step3, 0); + assertApproxEqAbs(liquidityStep3, liquidity / 2, 100); + } + + function test_pull_too_much() public { + uint amount = 10000; + RL.RoutingOrder memory order = getRoutingOrder(token0); + vm.expectRevert(); + router.pull(order, amount, true); + } + + function test_push_and_pull() public { + uint128 liquidity = getLiquidity(); + + uint amount = 1000; + deal($(token0), address(this), amount); + token0.approve(address(router), amount); + RL.RoutingOrder memory order = getRoutingOrder(token0); + router.push(order, amount); + + uint managerBalanceToken0Step2 = manager.balanceOf(user, token0); + uint128 liquidityStep2 = getLiquidity(); + + assertEq(managerBalanceToken0Step2, amount); + assertEq(liquidityStep2, liquidity); + + router.pull(order, amount, true); + + uint managerBalanceToken0Step3 = manager.balanceOf(user, token0); + uint128 liquidityStep3 = getLiquidity(); + + assertEq(managerBalanceToken0Step3, 0); + assertEq(liquidityStep3, liquidity); + } + + function test_push_imbalanced_ratios() public { + uint managerBalanceToken0 = manager.balanceOf(user, token0); + uint managerBalanceToken1 = manager.balanceOf(user, token1); + + uint128 liquidity = getLiquidity(); + + assertEq(managerBalanceToken0, 0); + assertEq(managerBalanceToken1, 0); + + uint amount0 = 1000; + deal($(token0), address(this), amount0); + token0.approve(address(router), amount0); + RL.RoutingOrder memory order = getRoutingOrder(token0); + router.push(order, amount0); + + uint managerBalanceToken0Step2 = manager.balanceOf(user, token0); + uint managerBalanceToken1Step2 = manager.balanceOf(user, token1); + uint128 liquidityStep2 = getLiquidity(); + + assertEq(managerBalanceToken0Step2, amount0); + assertEq(managerBalanceToken1Step2, 0); + assertEq(liquidityStep2, liquidity); + + uint amount1 = 500; + + deal($(token1), address(this), amount1); + token1.approve(address(router), amount1); + order = getRoutingOrder(token1); + router.push(order, amount1); + + uint managerBalanceToken0Step3 = manager.balanceOf(user, token0); + uint managerBalanceToken1Step3 = manager.balanceOf(user, token1); + uint128 liquidityStep3 = getLiquidity(); + + assertGt(managerBalanceToken0Step3, 0); + assertEq(managerBalanceToken1Step3, 0); + assertGt(liquidityStep3, liquidity); + } + + function test_pull_imbalanced_ratios() public { + uint managerBalanceToken0 = manager.balanceOf(user, token0); + uint managerBalanceToken1 = manager.balanceOf(user, token1); + + uint128 liquidity = getLiquidity(); + + assertEq(managerBalanceToken0, 0); + assertEq(managerBalanceToken1, 0); + + uint amount0 = 200; + RL.RoutingOrder memory order = getRoutingOrder(token0); + router.pull(order, amount0, true); + + uint managerBalanceToken0Step2 = manager.balanceOf(user, token0); + uint managerBalanceToken1Step2 = manager.balanceOf(user, token1); + uint128 liquidityStep2 = getLiquidity(); + + assertEq(managerBalanceToken0Step2, 0); + assertEq(managerBalanceToken1Step2, amount0); + assertLt(liquidityStep2, liquidity); + + uint amount1 = 500; + order = getRoutingOrder(token1); + router.pull(order, amount1, true); + + uint managerBalanceToken0Step3 = manager.balanceOf(user, token0); + uint managerBalanceToken1Step3 = manager.balanceOf(user, token1); + uint128 liquidityStep3 = getLiquidity(); + + assertEq(managerBalanceToken0Step3, amount1 - amount0); + assertEq(managerBalanceToken1Step3, 0); + assertLt(liquidityStep3, liquidityStep2); + } + + function test_pull_tokens_not_in_position() public { + uint amount = 1000; + RL.RoutingOrder memory order = getRoutingOrder(token2); + setLogic(token2); + vm.expectRevert("UniV3RoutingLogic/invalid-token"); + router.pull(order, amount, true); + } + + function test_take_balance() public { + uint push = 1000; + deal($(token0), address(this), push); + token0.approve(address(router), push); + RL.RoutingOrder memory order = getRoutingOrder(token0); + router.push(order, push); + + uint balanceInManager = manager.balanceOf(user, token0); + uint balanceBefore = token0.balanceOf(user); + + assertEq(balanceInManager, push); + assertEq(balanceBefore, 0); + + uint pull = 500; + vm.prank(user); + manager.retractAmount(user, token0, pull, user); + + uint balanceAfter = token0.balanceOf(user); + uint balanceInManagerAfter = manager.balanceOf(user, token0); + + assertEq(balanceAfter, pull); + assertEq(balanceInManagerAfter, push - pull); + } + + function test_cannot_withdraw_more() public { + uint push = 1000; + deal($(token0), address(this), push); + token0.approve(address(router), push); + RL.RoutingOrder memory order = getRoutingOrder(token0); + router.push(order, push); + + uint balanceInManager = manager.balanceOf(user, token0); + uint balanceBefore = token0.balanceOf(user); + + assertEq(balanceInManager, push); + assertEq(balanceBefore, 0); + + uint pull = push * 2; + deal($(token0), address(manager), pull); + vm.expectRevert("UniV3Manager/insufficient-balance"); + vm.prank(user); + manager.retractAmount(user, token0, pull, user); + } +} diff --git a/test/toy_strategies/Univ3Deployer.t.sol b/test/toy_strategies/Univ3Deployer.t.sol new file mode 100644 index 000000000..cee36308b --- /dev/null +++ b/test/toy_strategies/Univ3Deployer.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Univ3Deployer} from "@mgv-strats/src/toy_strategies/utils/Univ3Deployer.sol"; +import {StratTest, MgvReader, TestMaker, TestTaker, TestSender, console} from "@mgv-strats/test/lib/StratTest.sol"; +import {TestToken} from "@mgv/test/lib/tokens/TestToken.sol"; +import {PinnedPolygonFork} from "@mgv/test/lib/forks/Polygon.sol"; +import {PoolAddress} from "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/libraries/PoolAddress.sol"; +import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import {INonfungiblePositionManager} from + "@mgv-strats/src/strategies/vendor/uniswap/v3/periphery/interfaces/INonfungiblePositionManager.sol"; +import {TickMath} from "@mgv-strats/src/strategies/vendor/uniswap/v3/core/libraries/TickMath.sol"; + +contract Univ3Deployer_test is StratTest, Univ3Deployer { + PinnedPolygonFork fork; + + event PoolCreated( + address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool + ); + + function _getPoolKey(address token0, address token1, uint24 fee) + internal + pure + returns (PoolAddress.PoolKey memory poolKey) + { + poolKey = PoolAddress.getPoolKey(token0, token1, fee); + } + + function _getPool(PoolAddress.PoolKey memory poolKey) internal view returns (IUniswapV3Pool pool) { + return IUniswapV3Pool(PoolAddress.computeAddress(address(factory), poolKey)); + } + + function setUp() public override { + deployUniv3(); + + fork = new PinnedPolygonFork(39880000); + fork.setUp(); + + base = TestToken(fork.get("WETH.e")); + quote = TestToken(fork.get("DAI.e")); + } + + function test_position_manager() public { + assertEq(positionManager.name(), "Uniswap V3 Positions NFT-V1"); + assertEq(positionManager.symbol(), "UNI-V3-POS"); + assertEq(positionManager.factory(), address(factory)); + assertEq(positionManager.totalSupply(), 0); + } + + function test_factory() public { + assertEq(factory.owner(), address(this)); + assertEq(factory.feeAmountTickSpacing(500), 10); + assertEq(factory.feeAmountTickSpacing(3000), 60); + assertEq(factory.feeAmountTickSpacing(10000), 200); + } + + function test_create_pool() public { + IUniswapV3Pool exepextedPool = _getPool(PoolAddress.getPoolKey(address(base), address(quote), 500)); + vm.expectEmit(); + emit PoolCreated(address(base), address(quote), 500, 10, address(exepextedPool)); + address pool = factory.createPool(address(base), address(quote), 500); + assertEq(pool, factory.getPool(address(base), address(quote), 500)); + assertEq(pool, address(exepextedPool)); + + exepextedPool.initialize(TickMath.getSqrtRatioAtTick(0)); + + assertEq(exepextedPool.token0(), address(base)); + assertEq(exepextedPool.token1(), address(quote)); + assertEq(exepextedPool.fee(), 500); + assertEq(exepextedPool.tickSpacing(), 10); + (,,,,,, bool unlocked) = exepextedPool.slot0(); + assertTrue(unlocked); + } + + function test_addLiquidity() public { + address pool = factory.createPool(address(base), address(quote), 500); + IUniswapV3Pool(pool).initialize(TickMath.getSqrtRatioAtTick(0)); + + INonfungiblePositionManager.MintParams memory params; + params.token0 = address(base); + params.token1 = address(quote); + params.fee = 500; + params.tickLower = -500; + params.tickUpper = 500; + params.deadline = block.timestamp + 1000; + params.amount0Desired = 1000; + params.amount1Desired = 1000; + params.recipient = address(this); + + // base.mint(amount0Desired); + // quote.mint(amount1Desired); + deal($(base), address(this), params.amount0Desired); + deal($(quote), address(this), params.amount1Desired); + + base.approve(address(positionManager), params.amount0Desired); + quote.approve(address(positionManager), params.amount1Desired); + + positionManager.mint(params); + + assertEq(positionManager.balanceOf(address(this)), 1); + } +} diff --git a/uni-out/.gitignore b/uni-out/.gitignore new file mode 100644 index 000000000..314f02b1b --- /dev/null +++ b/uni-out/.gitignore @@ -0,0 +1 @@ +*.txt \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 88b760838..70ab670a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -139,6 +139,8 @@ __metadata: "@mangrovedao/mangrove-core": ^2.1.1 "@mangrovedao/mangrove-deployments": ^2.2.2 "@types/node": ^20.11.5 + "@uniswap/v3-core": ^1.0.1 + "@uniswap/v3-periphery": ^1.4.4 husky: ^8.0.3 lint-staged: ^15.2.0 micromatch: ^4.0.5 @@ -154,6 +156,13 @@ __metadata: languageName: unknown linkType: soft +"@openzeppelin/contracts@npm:3.4.2-solc-0.7": + version: 3.4.2-solc-0.7 + resolution: "@openzeppelin/contracts@npm:3.4.2-solc-0.7" + checksum: 1a6048f31ed560c34429a05e534102c51124ecaf113aca7ebeb7897cfaaf61007cdd7952374c282adaeb79b04ee86ee80b16eed28b62fc6d60e3ffcd7a696895 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -271,6 +280,40 @@ __metadata: languageName: node linkType: hard +"@uniswap/lib@npm:^4.0.1-alpha": + version: 4.0.1-alpha + resolution: "@uniswap/lib@npm:4.0.1-alpha" + checksum: d7bbacccef40966af16c7e215ab085f575686d316b2802c9e1cfd03f7ad351970e547535670a28b2279c3cfcc4fb02888614c46f94efe2987af2309f3ec86127 + languageName: node + linkType: hard + +"@uniswap/v2-core@npm:^1.0.1": + version: 1.0.1 + resolution: "@uniswap/v2-core@npm:1.0.1" + checksum: eaa118fe45eac2e80b7468547ce2cde12bd3c8157555d2e40e0462a788c9506c6295247b511382da85e44a89ad92aff7bb3433b23bfbd2eeea23942ecd46e979 + languageName: node + linkType: hard + +"@uniswap/v3-core@npm:^1.0.0, @uniswap/v3-core@npm:^1.0.1": + version: 1.0.1 + resolution: "@uniswap/v3-core@npm:1.0.1" + checksum: 4bfd8b218391a3d9efde44e0f984cfec3bc25889cd4d1386766828521006e71b210a3583ee32b52f3c81d384af9e8c39f471f2229e9af4d50da6801446ecb3e4 + languageName: node + linkType: hard + +"@uniswap/v3-periphery@npm:^1.4.4": + version: 1.4.4 + resolution: "@uniswap/v3-periphery@npm:1.4.4" + dependencies: + "@openzeppelin/contracts": 3.4.2-solc-0.7 + "@uniswap/lib": ^4.0.1-alpha + "@uniswap/v2-core": ^1.0.1 + "@uniswap/v3-core": ^1.0.0 + base64-sol: 1.0.1 + checksum: 48b57f1f648cb002935421ac1770666ab3c0263885a03c769985b06501b88d513a435c8edc439b41ac5aef7ad40a11038a3561f7e828cce5ae6ec2c77742f1af + languageName: node + linkType: hard + "acorn-walk@npm:^8.1.1": version: 8.3.0 resolution: "acorn-walk@npm:8.3.0" @@ -417,6 +460,13 @@ __metadata: languageName: node linkType: hard +"base64-sol@npm:1.0.1": + version: 1.0.1 + resolution: "base64-sol@npm:1.0.1" + checksum: be0f9e8cf3c744256913223fbae8187773f530cc096e98a77f49ef0bd6cedeb294d15a784e439419f7cb99f07bf85b08999169feafafa1a9e29c3affc0bc6d0a + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11"