diff --git a/.gitignore b/.gitignore index cdfce1c25..ef0602278 100644 --- a/.gitignore +++ b/.gitignore @@ -123,4 +123,5 @@ out.sol out.sol pickleJarOut.sol -typechain-types/ \ No newline at end of file +typechain-types/ +.vim diff --git a/.prettierrc b/.prettierrc index cb4c77cfb..5061e21f3 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,26 +1,22 @@ { + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false, + "explicitTypes": "always", "overrides": [ { "files": "*.sol", "options": { - "printWidth": 120, - "tabWidth": 4, - "useTabs": false, - "singleQuote": false, - "bracketSpacing": false, - "explicitTypes": "always" + "tabWidth": 4 } }, { - "files": "*.js", + "files": "*.ts", "options": { - "printWidth": 120, - "tabWidth": 2, - "useTabs": false, - "singleQuote": false, - "bracketSpacing": false, - "explicitTypes": "always" + "parser": "typescript" } } ] -} +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 59d68f298..781180326 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,9 +3,8 @@ import "@nomicfoundation/hardhat-toolbox"; import "@nomiclabs/hardhat-vyper"; import "hardhat-deploy"; import "hardhat-contract-sizer"; -import { removeConsoleLog } from "hardhat-preprocessor"; -import { HardhatUserConfig, subtask, task, types } from "hardhat/config"; -import fs from "fs"; +import {removeConsoleLog} from "hardhat-preprocessor"; +import {HardhatUserConfig} from "hardhat/config"; import * as dotenv from "dotenv"; dotenv.config(); @@ -37,34 +36,29 @@ const config: HardhatUserConfig = { networks: { hardhat: { forking: { - url: `https://evm.kava.io`, - // ignoreUnknownTxType: true, // needed to work with patched Hardhat + Arbitrum Nitro - blockNumber: 2464633, + url: "https://evm2.kava.io", }, + chainId: 2222, + accounts: { mnemonic: process.env.MNEMONIC, }, - // mining: { - // auto: false, - // interval: [4000, 6000], - // }, hardfork: "london", gasPrice: "auto", - gas: 6500000, + blockGasLimit: 300_000_000, }, mainnet: { - // url: `https://rpc.flashbots.net`, - url: `https://mainnet.infura.io/v3/${process.env.INFURA_KEY}`, + url: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY_MAINNET}`, accounts: [process.env.PRIVATE_KEY ?? ""], chainId: 1, }, - matic: { - url: `https://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY_MATIC}`, + polygon: { + url: `https://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY_POLYGON}`, accounts: [process.env.PRIVATE_KEY ?? ""], chainId: 137, }, arbitrumOne: { - url: `https://1rpc.io/arb`, + url: `https://arb-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY_ARBITRUM}`, accounts: [process.env.PRIVATE_KEY ?? ""], chainId: 42161, }, @@ -103,6 +97,11 @@ const config: HardhatUserConfig = { accounts: [process.env.PRIVATE_KEY ?? ""], chainId: 100, }, + kava: { + url: "https://evm2.kava.io", + accounts: [process.env.PRIVATE_KEY ?? ""], + chainId: 2222, + }, }, contractSizer: { alphaSort: true, @@ -110,16 +109,17 @@ const config: HardhatUserConfig = { }, etherscan: { apiKey: { - mainnet: process.env.ETHERSCAN_APIKEY ?? "", - aurora: process.env.AURORASCAN_APIKEY ?? "", + mainnet: process.env.ETHERSCAN_APIKEY_ETHEREUM ?? "", + aurora: process.env.ETHERSCAN_APIKEY_AURORA ?? "", xdai: process.env.BLOCKSCOUT_APIKEY_GNOSIS ?? "", optimisticEthereum: process.env.ETHERSCAN_APIKEY_OPTIMISM ?? "", arbitrumOne: process.env.ETHERSCAN_APIKEY_ARBISCAN ?? "", opera: process.env.ETHERSCAN_APIKEY_FANTOM ?? "", + polygon: process.env.ETHERSCAN_APIKEY_POLYGON ?? "", }, }, paths: { - sources: "./src/strategies/kava", + sources: "./src", tests: "./src/tests/strategies", cache: "./cache", artifacts: "./artifacts", @@ -136,147 +136,14 @@ const config: HardhatUserConfig = { gasPrice: 32, }, preprocess: { - eachLine: removeConsoleLog( - (hre) => - hre.network.name !== "hardhat" && hre.network.name !== "localhost" - ), + eachLine: removeConsoleLog((hre) => hre.network.name !== "hardhat" && hre.network.name !== "localhost"), }, mocha: { timeout: 20000000, }, vyper: { - compilers: [{ version: "0.2.4" }, { version: "0.2.7" }], + compilers: [{version: "0.2.4"}, {version: "0.2.7"}], }, }; -function getSortedFiles(dependenciesGraph) { - const tsort = require("tsort"); - const graph = tsort(); - - const filesMap = {}; - const resolvedFiles = dependenciesGraph.getResolvedFiles(); - resolvedFiles.forEach((f) => (filesMap[f.sourceName] = f)); - - for (const [from, deps] of dependenciesGraph.entries()) { - for (const to of deps) { - graph.add(to.sourceName, from.sourceName); - } - } - - const topologicalSortedNames = graph.sort(); - - // If an entry has no dependency it won't be included in the graph, so we - // add them and then dedup the array - const withEntries = topologicalSortedNames.concat( - resolvedFiles.map((f) => f.sourceName) - ); - - const sortedNames = [...new Set(withEntries)]; - return sortedNames.map((n: any) => filesMap[n]); -} - -function getFileWithoutImports(resolvedFile) { - const IMPORT_SOLIDITY_REGEX = /^\s*import(\s+)[\s\S]*?;\s*$/gm; - - return resolvedFile.content.rawContent - .replace(IMPORT_SOLIDITY_REGEX, "") - .trim(); -} - -subtask( - "flat:get-flattened-sources", - "Returns all contracts and their dependencies flattened" -) - .addOptionalParam("files", undefined, undefined, types.any) - .addOptionalParam("output", undefined, undefined, types.string) - .setAction(async ({ files, output }, { run }) => { - const dependencyGraph = await run("flat:get-dependency-graph", { files }); - console.log(dependencyGraph); - - let flattened = ""; - - if (dependencyGraph.getResolvedFiles().length === 0) { - return flattened; - } - - const sortedFiles = getSortedFiles(dependencyGraph); - - let isFirst = true; - for (const file of sortedFiles) { - if (!isFirst) { - flattened += "\n"; - } - flattened += `// File ${file.getVersionedName()}\n`; - flattened += `${getFileWithoutImports(file)}\n`; - - isFirst = false; - } - - // Remove every line started with "// SPDX-License-Identifier:" - flattened = flattened.replace( - /SPDX-License-Identifier:/gm, - "License-Identifier:" - ); - - flattened = `// SPDX-License-Identifier: MIXED\n\n${flattened}`; - - // Remove every line started with "pragma experimental ABIEncoderV2;" except the first one - flattened = flattened.replace( - /pragma experimental ABIEncoderV2;\n/gm, - ( - (i) => (m) => - !i++ ? m : "" - )(0) - ); - - flattened = flattened.trim(); - if (output) { - console.log("Writing to", output); - fs.writeFileSync(output, flattened); - return ""; - } - return flattened; - }); - -subtask("flat:get-dependency-graph") - .addOptionalParam("files", undefined, undefined, types.any) - .setAction(async ({ files }, { run }) => { - const sourcePaths = - files === undefined - ? await run("compile:solidity:get-source-paths") - : files.map((f) => fs.realpathSync(f)); - - const sourceNames = await run("compile:solidity:get-source-names", { - sourcePaths, - }); - - const dependencyGraph = await run("compile:solidity:get-dependency-graph", { - sourceNames, - }); - - return dependencyGraph; - }); - -task("flat", "Flattens and prints contracts and their dependencies") - .addOptionalVariadicPositionalParam( - "files", - "The files to flatten", - undefined, - types.inputFile - ) - .addOptionalParam( - "output", - "Specify the output file", - undefined, - types.string - ) - .setAction(async ({ files, output }, { run }) => { - console.log( - await run("flat:get-flattened-sources", { - files, - output, - }) - ); - }); - export default config; diff --git a/package.json b/package.json index 98256721a..eddc11420 100644 --- a/package.json +++ b/package.json @@ -15,32 +15,35 @@ "dill:wait": "npx hardhat run ./hardhat-scripts/dill-test/3.wait.js --network localhost" }, "devDependencies": { - "@ethersproject/abi": "^5.6.4", - "@ethersproject/hardware-wallets": "^5.6.1", - "@ethersproject/providers": "^5.6.8", + "@ethersproject/abi": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hardware-wallets": "^5.7.0", + "@ethersproject/providers": "^5.7.2", "@nomicfoundation/hardhat-chai-matchers": "^1.0.2", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomicfoundation/hardhat-toolbox": "^1.0.2", - "@nomiclabs/hardhat-ethers": "^2.1.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.7", + "@nomicfoundation/hardhat-toolbox": "^2.0.1", + "@nomiclabs/hardhat-ethers": "^2.2.2", "@nomiclabs/hardhat-etherscan": "^3.1.0", "@nomiclabs/hardhat-vyper": "^3.0.0", "@nomiclabs/hardhat-web3": "^2.0.0", "@openzeppelin/contracts": "^3.4.1", "@openzeppelin/contracts-upgradeable": "^3.4.1", "@openzeppelin/test-helpers": "^0.5.10", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", + "@safe-global/safe-core-sdk": "^3.3.1", + "@safe-global/safe-ethers-lib": "https://github.com/MisterMard/safe-core-sdk/raw/release/patched/packages/safe-ethers-lib/safe-global-safe-ethers-lib-1.9.2.tgz", + "@typechain/ethers-v5": "^10.1.1", + "@typechain/hardhat": "^6.1.4", "@types/chai": "^4.3.1", "@types/mocha": "^9.1.1", "@types/node": "^18.6.2", - "bn.js": "^5.2.1", + "bn.js": "^4.2.1", "chai": "^4.3.4", "dotenv": "^8.2.0", "eslint": "^7.10.0", - "ethers": "^5.1.3", - "hardhat": "npm:@gzeoneth/hardhat@2.10.2-gzeon-e972f1dda", + "ethers": "^5.7.2", + "hardhat": "^2.12.6", "hardhat-contract-sizer": "^2.1.1", - "hardhat-deploy": "^0.11.12", + "hardhat-deploy": "^0.11.22", "hardhat-gas-reporter": "^1.0.4", "hardhat-preprocessor": "^0.1.4", "mocha": "^9.0.2", @@ -48,7 +51,7 @@ "prettier-plugin-solidity": "^1.0.0-beta.9", "solidity-coverage": "^0.7.16", "ts-node": "^10.9.1", - "typechain": "^8.1.0", + "typechain": "^8.1.1", "typescript": "^4.7.4", "web3": "^1.7.4" }, diff --git a/scripts/deployer/constants.ts b/scripts/deployer/constants.ts new file mode 100644 index 000000000..60d2930a4 --- /dev/null +++ b/scripts/deployer/constants.ts @@ -0,0 +1,127 @@ + +export interface ChainAddresses { + governance: string; + strategist: string; + controller: string; + timelock: string; + native: string; +} +enum Chains { + ARBITRUM = "arbitrum", + ARBITRUM_V3 = "arbitrumv3", + OPTIMISM = "optimism", + OPTIMISM_OLD = "optimism_old", //old controller + OPTIMISM_V3 = "optimismv3", // univ3 controller + GNOSIS = "gnosis", + FANTOM_1 = "fantom1", + FANTOM_2 = "fantom2", + MATIC = "matic", + MATIC_V3 = "maticv3", + MAINNET = "mainnet", +} + +export interface DeploymentStateObject { + [strategyName: string]: { + name: string; + strategy?: string; + jar?: string; + want?: string; // this is the pool address for univ3 + wantApproveTx?: string; // !for univ3 + token0ApproveTx?: string; // for univ3 + token1ApproveTx?: string; // for univ3 + token0DepositTx?: string; // for univ3 + token1DepositTx?: string; // for univ3 + jarSet?: boolean; + stratSet?: boolean; + depositTx?: string; + earnTx?: string; + harvestTx?: string; + rebalanceTx?: string; + }; +} + +export type ConstructorArguments = (string | number)[]; + +export const ADDRESSES: { [key in Chains]: ChainAddresses } = { + arbitrum: { + governance: "0xf02CeB58d549E4b403e8F85FBBaEe4c5dfA47c01", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0x55D5BCEf2BFD4921B8790525FF87919c2E26bD03", + timelock: "0xf02CeB58d549E4b403e8F85FBBaEe4c5dfA47c01", + native: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + }, + arbitrumv3: { + governance: "0xf02CeB58d549E4b403e8F85FBBaEe4c5dfA47c01", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0xf968f18512A9BdDD9C3a166dd253B24c27a455DD", + timelock: "0xf02CeB58d549E4b403e8F85FBBaEe4c5dfA47c01", + native: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + }, + optimism_old: { + governance: "0x7A79e2e867d36a91Bb47e0929787305c95E793C5", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0xA1D43D97Fc5F1026597c67805aA02aAe558E0FeF", + timelock: "0x7A79e2e867d36a91Bb47e0929787305c95E793C5", + native: "0x4200000000000000000000000000000000000006", + }, + optimism: { + governance: "0x7A79e2e867d36a91Bb47e0929787305c95E793C5", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0xeEDeF926D3d7C9628c8620B5a018c102F413cDB7", + timelock: "0x7A79e2e867d36a91Bb47e0929787305c95E793C5", + native: "0x4200000000000000000000000000000000000006", + }, + optimismv3: { + governance: "0x7A79e2e867d36a91Bb47e0929787305c95E793C5", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0xa936511d24F9488Db343AfDdccBf78AD28bd3F42", + timelock: "0x7A79e2e867d36a91Bb47e0929787305c95E793C5", + native: "0x4200000000000000000000000000000000000006", + }, + gnosis: { + governance: "0x986e64622FFB6b95B0bE00076051807433258B46", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0xe5E231De20C68AabB8D669f87971aE57E2AbF680", + timelock: "0x986e64622FFB6b95B0bE00076051807433258B46", + native: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", + }, + fantom1: { + governance: "0xE4ee7EdDDBEBDA077975505d11dEcb16498264fB", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0xB1698A97b497c998b2B2291bb5C48D1d6075836a", + timelock: "0xE4ee7EdDDBEBDA077975505d11dEcb16498264fB", + native: "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83", + }, + fantom2: { + governance: "0xE4ee7EdDDBEBDA077975505d11dEcb16498264fB", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0xc335740c951F45200b38C5Ca84F0A9663b51AEC6", + timelock: "0xE4ee7EdDDBEBDA077975505d11dEcb16498264fB", + native: "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83", + }, + matic: { + governance: "0xEae55893cC8637c16CF93D43B38aa022d689Fa62", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0x83074F0aB8EDD2c1508D3F657CeB5F27f6092d09", + timelock: "0xEae55893cC8637c16CF93D43B38aa022d689Fa62", + native: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + }, + maticv3: { + governance: "0xEae55893cC8637c16CF93D43B38aa022d689Fa62", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0x90Ee5481A78A23a24a4290EEc42E8Ad0FD3B4AC3", + timelock: "0xEae55893cC8637c16CF93D43B38aa022d689Fa62", + native: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + }, + mainnet: { + governance: "0x9d074E37d408542FD38be78848e8814AFB38db17", + strategist: "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5", + controller: "0x6847259b2B3A4c17e7c43C54409810aF48bA5210", + timelock: "0xD92c7fAa0Ca0e6AE4918f3a83d9832d9CAEAA0d3", + native: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + }, +}; + +export const OBJECT_FILE_NAME: string = "deployment-state-object.json"; // deployment state object file name +export const CONTROLLERV4_CONTRACT = "src/controller-v4.sol:ControllerV4"; +export const JAR_CONTRACT = "src/pickle-jar.sol:PickleJar"; diff --git a/scripts/deployer/deployment-state-object.json b/scripts/deployer/deployment-state-object.json new file mode 100644 index 000000000..052d1adf0 --- /dev/null +++ b/scripts/deployer/deployment-state-object.json @@ -0,0 +1,7 @@ +{ + "ExampleStrategy": { + "name": "ExampleStrategy", + "jarSet": false, + "stratSet": false + } +} \ No newline at end of file diff --git a/scripts/deployer/flat.js b/scripts/deployer/flat.js new file mode 100644 index 000000000..9938d2328 --- /dev/null +++ b/scripts/deployer/flat.js @@ -0,0 +1,113 @@ +const {task, subtask, types} = require("hardhat/config"); +const fs = require("fs"); + +function getSortedFiles(dependenciesGraph) { + const tsort = require("tsort"); + const graph = tsort(); + + const filesMap = {}; + const resolvedFiles = dependenciesGraph.getResolvedFiles(); + resolvedFiles.forEach((f) => (filesMap[f.sourceName] = f)); + + for (const [from, deps] of dependenciesGraph.entries()) { + for (const to of deps) { + graph.add(to.sourceName, from.sourceName); + } + } + + const topologicalSortedNames = graph.sort(); + + // If an entry has no dependency it won't be included in the graph, so we + // add them and then dedup the array + const withEntries = topologicalSortedNames.concat(resolvedFiles.map((f) => f.sourceName)); + + const sortedNames = [...new Set(withEntries)]; + return sortedNames.map((n) => filesMap[n]); +} + +function getFileWithoutImports(resolvedFile) { + const IMPORT_SOLIDITY_REGEX = /^\s*import(\s+)[\s\S]*?;\s*$/gm; + + return resolvedFile.content.rawContent.replace(IMPORT_SOLIDITY_REGEX, "").trim(); +} + +subtask("flat:get-flattened-sources", "Returns all contracts and their dependencies flattened") + .addOptionalParam("files", undefined, undefined, types.any) + .addOptionalParam("output", undefined, undefined, types.string) + .setAction(async ({files, output}, {run}) => { + const dependencyGraph = await run("flat:get-dependency-graph", {files}); + + let flattened = ""; + + if (dependencyGraph.getResolvedFiles().length === 0) { + return flattened; + } + + const sortedFiles = getSortedFiles(dependencyGraph); + + let isFirst = true; + for (const file of sortedFiles) { + if (!isFirst) { + flattened += "\n"; + } + flattened += `// File ${file.getVersionedName()}\n`; + flattened += `${getFileWithoutImports(file)}\n`; + + isFirst = false; + } + + // Remove every line started with "// SPDX-License-Identifier:" + flattened = flattened.replace(/SPDX-License-Identifier:/gm, "License-Identifier:"); + + flattened = `// SPDX-License-Identifier: MIXED\n\n${flattened}`; + + // Remove every line started with "pragma experimental ABIEncoderV2;" except the first one + flattened = flattened.replace( + /pragma experimental ABIEncoderV2;\n/gm, + ( + (i) => (m) => + !i++ ? m : "" + )(0) + ); + + flattened = flattened.trim(); + if (output) { + console.log("Writing to", output); + fs.writeFileSync(output, flattened); + return ""; + } + return flattened; + }); + +subtask("flat:get-dependency-graph") + .addOptionalParam("files", undefined, undefined, types.any) + .setAction(async ({files}, {run}) => { + const sourcePaths = + files === undefined ? await run("compile:solidity:get-source-paths") : files.map((f) => fs.realpathSync(f)); + + const sourceNames = await run("compile:solidity:get-source-names", { + sourcePaths, + }); + + const dependencyGraph = await run("compile:solidity:get-dependency-graph", {sourceNames}); + + return dependencyGraph; + }); + +task("flat", "Flattens and prints contracts and their dependencies") + .addOptionalVariadicPositionalParam("files", "The file or contract name to flatten", undefined, types.string) + .addOptionalParam("output", "Specify the output file", undefined, types.string) + .setAction(async ({files, output}, {run}) => { + if (!output) { + output = "./flat/" + files[0] + ".sol"; + } + files = files.map((file) => { + return file.endsWith(".sol") ? file : "./contracts/" + file + ".sol"; + }); + await run("flat:get-flattened-sources", { + files, + output, + }); + }); + +module.exports = {}; diff --git a/scripts/deployer/procedures.ts b/scripts/deployer/procedures.ts new file mode 100644 index 000000000..61d47f795 --- /dev/null +++ b/scripts/deployer/procedures.ts @@ -0,0 +1,558 @@ +import { ethers } from "hardhat"; +import { BigNumber, Contract } from "ethers"; +import { TransactionReceipt, TransactionResponse } from "@ethersproject/providers"; +import stateObjectJson from "./deployment-state-object.json"; // deployment state object +import { + deployJar, + deployJarV3, + deployStrategy, + deployStrategyV3, + flatDeployAndVerifyContract, + persistify, + sleep, + verifyContract, + verifyJar, +} from "./utils"; +import { ADDRESSES, ChainAddresses, DeploymentStateObject } from "./constants"; + +const stateObject: DeploymentStateObject = stateObjectJson; + +export const deployPickleTokenAnyswap = async () => { + const tokenContract = "src/optimism/pickle-token.sol:AnyswapV6ERC20"; + const name = "PickleToken"; + const symbol = "PICKLE"; + const decimals = 18; + const vault = await ethers.getSigners().then((x) => x[0].address); + const underlying = ethers.constants.AddressZero; + const constructorArgs = [name, symbol, decimals, underlying, vault]; + + const tokenAddress = await flatDeployAndVerifyContract(tokenContract, constructorArgs, false); + console.log(`PickleToken Successfully deployed at ${tokenAddress}`); +} + +export const deployStratAndJar = async ( + strategyContract: string, + jarContract: string, + chainAddresses: ChainAddresses +) => { + const deployer = await ethers.getSigners().then((x) => x[0]); + console.log(`Deployer: ${deployer.address}`); + + const name = strategyContract.substring( + strategyContract.lastIndexOf(":") + 1 + ); + + let strategy: Contract | undefined, jar: Contract | undefined; + + // Retrieve deployed contracts if present + if (stateObject[name]) { + const stratAddr = stateObject[name].strategy; + const jarAddr = stateObject[name].jar; + if (stratAddr) { + strategy = await ethers.getContractAt(strategyContract, stratAddr); + } + if (jarAddr) { + jar = await ethers.getContractAt(jarContract, jarAddr); + } + } else { + stateObject[name] = { name: name }; + } + + persistify(stateObject); + + let deploymentSuccess = false; + try { + if (!strategy) { + // Deploy Strategy contract + console.log(`\nDeploying ${name}...`); + const strategyAddr = await deployStrategy( + strategyContract, + chainAddresses.governance, + chainAddresses.strategist, + chainAddresses.controller, + chainAddresses.timelock + ); + + stateObject[name].strategy = strategyAddr; + persistify(stateObject); + strategy = await ethers.getContractAt(strategyContract, strategyAddr); + console.log(`✔️ Strategy deployed at: ${strategy.address}`); + } + + // TODO: Check for deployer address want balance + + if (!jar) { + // Get Want details + const want: string = await strategy.want(); + stateObject[name].want = want; + + // Deploy PickleJar contract + console.log("\nDeploying jar..."); + + const jarAddr = await deployJar( + jarContract, + want, + chainAddresses.governance, + chainAddresses.timelock, + chainAddresses.controller + ); + stateObject[name].jar = jarAddr; + persistify(stateObject); + jar = await ethers.getContractAt(jarContract, jarAddr); + console.log(`✔️ PickleJar deployed at: ${jar.address}`); + } + + if (!stateObject[name].wantApproveTx) { + // Log Want + const want = await ethers.getContractAt( + "src/lib/erc20.sol:ERC20", + await strategy.want() + ); + console.log(`Want address is: ${stateObject[name].want}`); + + // Approve want for deposit + console.log(`Approving token0 for deposit...`); + const wantApproveTx: TransactionReceipt = await ( + await want.approve(jar?.address, ethers.constants.MaxUint256) + ).wait(); + stateObject[name].wantApproveTx = wantApproveTx.transactionHash; + persistify(stateObject); + console.log(`✔️ Successfully approved Jar to spend want`); + } + deploymentSuccess = true; + } catch (e) { + console.log(`❌❌ Oops something went wrong...`); + console.error(e); + } + // Deployment Report + const report = ` + ${stateObject[name].name} - ${deploymentSuccess ? "Fully Deployed" : "DEPLOYMENT FAILED!" + } + jar:${stateObject[name].jar} + want:${stateObject[name].want} + strategy:${stateObject[name].strategy} + ---------------------------- + `; + console.log(report); + return deploymentSuccess; +}; + +export const testStratAndJar = async ( + strategyContract: string, + jarContract: string, + controllerContract: string +) => { + let testSuccessful = false; + const deployer = await ethers.getSigners().then(x => x[0]); + + const name = strategyContract.substring( + strategyContract.lastIndexOf(":") + 1 + ); + console.log(`Deployer: ${deployer.address} Strategy: ${name}`); + + if (!(stateObject[name] && stateObject[name].strategy && stateObject[name].jar && stateObject[name].want && stateObject[name].wantApproveTx)) + throw `❌❌ ${name} is not fully deployed!`; + + const strategy = await ethers.getContractAt( + strategyContract, + stateObject[name].strategy + ); + const jar = await ethers.getContractAt(jarContract, stateObject[name].jar); + const want = await ethers.getContractAt("src/lib/erc20.sol:ERC20", stateObject[name].want); + const controller = await ethers.getContractAt( + controllerContract, + await jar.controller() + ); + + + if (!stateObject[name].depositTx) { + const wantAllowance: BigNumber = await want.allowance(deployer.address, jar.address); + const deployerBalance: BigNumber = await want.balanceOf(deployer.address); + + // Sanity checks + if (deployerBalance.eq("0") || wantAllowance.lt(deployerBalance)) { + throw `❌❌ jar cannot spend tokens from deployer address!\n\twant allowance: ${wantAllowance.toString()}\n\tdeployer balance: ${deployerBalance.toString()}`; + } + if ((await controller.jars(want.address)) != jar.address) + throw "❌❌ jar is not set on the controller!"; + if ((await controller.strategies(want.address)) != strategy.address) + throw "❌❌ strategy is not set on the controller!"; + // Initial deposit + console.log("\nDepositing in jar..."); + try { + const estimate = await jar.estimateGas.deposit(deployerBalance); + const opts = { gasLimit: estimate.mul(2) }; + const receipt: TransactionReceipt = await jar.deposit(deployerBalance, opts).then((x: TransactionResponse) => x.wait()); + stateObject[name].depositTx = receipt.transactionHash; + persistify(stateObject); + console.log("✔️ Want deposited successfully"); + } catch (error) { + console.log("❌❌ failed depositing want into the jar!"); + throw error; + } + } + + if (!stateObject[name].earnTx) { + // Deposit Want + console.log("\nCalling earn on Jar..."); + const estimate = await jar.estimateGas.earn(); + const opts = { gasLimit: estimate.mul(2) }; + const receipt: TransactionReceipt = await jar.earn(opts).then((x: TransactionResponse) => x.wait()); + stateObject[name].earnTx = receipt.transactionHash; + persistify(stateObject); + console.log("✔️ Earn called successfully"); + } + + if (!stateObject[name].harvestTx) { + // Harvest + const ratioBefore = await jar.getRatio(); + console.log( + `\nWaiting for 60s before harvesting...` + ); + await sleep(60); + console.log("Calling harvest..."); + const estimate = await strategy.estimateGas.harvest(); + const opts = { gasLimit: estimate.mul(2) }; + const receipt: TransactionReceipt = await strategy.harvest(opts).then((x: TransactionResponse) => x.wait()); + await sleep(10); + const ratioAfter = await jar.getRatio(); + + if (ratioAfter.gt(ratioBefore)) { + console.log(`✔️ Harvest was successful`); + stateObject[name].harvestTx = receipt.transactionHash; + persistify(stateObject); + } else { + throw (`❌❌ Harvest failed, ratio has not increased`); + } + } + testSuccessful = true; + + // Script Report + const report = ` + ${stateObject[name].name} - ${stateObject[name].harvestTx ? "Fully Tested" : "TEST FAILED!" + } + jar:${stateObject[name].jar} + want:${stateObject[name].want} + strategy:${stateObject[name].strategy} + ---------------------------- + `; + console.log(report); + return testSuccessful; +}; + +// TODO fix this one +export const deployStratAndJarV3 = async ( + strategyContract: string, + jarContract: string, + tickMultiplier: number, + chainAddresses: ChainAddresses +) => { + const deployer = await ethers.getSigners().then((x) => x[0]); + console.log(`Deployer: ${deployer.address}`); + + const name = strategyContract.substring( + strategyContract.lastIndexOf(":") + 1 + ); + + let strategy: Contract | undefined, jar: Contract | undefined; + + // Retrieve deployed contracts if present + if (stateObject[name]) { + const stratAddr = stateObject[name].strategy; + const jarAddr = stateObject[name].jar; + if (stratAddr) { + strategy = await ethers.getContractAt(strategyContract, stratAddr); + } + if (jarAddr) { + jar = await ethers.getContractAt(jarContract, jarAddr); + } + } else { + stateObject[name] = { name: name }; + } + + persistify(stateObject); + + let deploymentSuccess = false; + try { + if (!strategy) { + // Deploy Strategy contract + console.log(`\nDeploying ${name}...`); + const strategyAddr = await deployStrategyV3( + strategyContract, + tickMultiplier, + chainAddresses.governance, + chainAddresses.strategist, + chainAddresses.controller, + chainAddresses.timelock + ); + + stateObject[name].strategy = strategyAddr; + persistify(stateObject); + strategy = await ethers.getContractAt(strategyContract, strategyAddr); + console.log(`✔️ Strategy deployed at: ${strategy.address}`); + } + + // TODO: Check for deployer address want balance + + if (!jar) { + // Get Want details + const want: string = await strategy.pool(); + stateObject[name].want = want; + + const token0 = await ethers.getContractAt( + "src/lib/erc20.sol:ERC20", + await strategy.token0() + ); + const token1 = await ethers.getContractAt( + "src/lib/erc20.sol:ERC20", + await strategy.token1() + ); + const native = await ethers.getContractAt( + "src/lib/erc20.sol:ERC20", + await strategy.native() + ); + const token0Symbol = await token0.symbol(); + const token1Symbol = await token1.symbol(); + + // Deploy PickleJar contract + console.log("\nDeploying jar..."); + + const jarName = `pickling ${token0Symbol}/${token1Symbol} Jar`; + const jarSymbol = `p${token0Symbol}${token1Symbol}`; + + const jarAddr = await deployJarV3( + jarContract, + jarName, + jarSymbol, + want, + native.address, + chainAddresses.governance, + chainAddresses.timelock, + chainAddresses.controller + ); + stateObject[name].jar = jarAddr; + persistify(stateObject); + jar = await ethers.getContractAt(jarContract, jarAddr); + console.log(`✔️ PickleJar deployed at: ${jar.address}`); + } + + let wantTokensApproved: boolean = !!( + stateObject[name].token0ApproveTx && stateObject[name].token1ApproveTx + ); + if (!wantTokensApproved) { + // Log Want + const token0 = await ethers.getContractAt( + "src/lib/erc20.sol:ERC20", + await strategy.token0() + ); + const token1 = await ethers.getContractAt( + "src/lib/erc20.sol:ERC20", + await strategy.token1() + ); + console.log(`Pool address is: ${stateObject[name].want}`); + console.log(`Token0 address: ${token0.address}`); + console.log(`Token1 address: ${token1.address}`); + + // Approve tokens for deposit + if (!stateObject[name].token0ApproveTx) { + console.log(`Approving token0 for deposit...`); + const approveToken0Tx: TransactionReceipt = await ( + await token0.approve(jar?.address, ethers.constants.MaxUint256) + ).wait(); + stateObject[name].token0ApproveTx = approveToken0Tx.transactionHash; + persistify(stateObject); + console.log(`✔️ Successfully approved Jar to spend token0`); + } + if (!stateObject[name].token1ApproveTx) { + console.log(`Approving token1 for deposit...`); + const approveToken1Tx: TransactionReceipt = await ( + await token1.approve(jar?.address, ethers.constants.MaxUint256) + ).wait(); + stateObject[name].token1ApproveTx = approveToken1Tx.transactionHash; + persistify(stateObject); + console.log(`✔️ Successfully approved Jar to spend token1`); + } + } + deploymentSuccess = true; + } catch (e) { + console.log(`❌❌ Oops something went wrong...`); + console.error(e); + } + // Deployment Report + const report = ` + ${stateObject[name].name} - ${deploymentSuccess ? "Fully Deployed" : "DEPLOYMENT FAILED!" + } + jar:\t${stateObject[name].jar} + want:\t${stateObject[name].want} + strategy:\t${stateObject[name].strategy} + ---------------------------- + `; + console.log(report); +}; + + +// TODO fix this one +//export const testStratAndJarV3 = async ( +// strategyContract: string, +// jarContract: string, +// controllerContract: string +//) => { +// const deployer = await ethers.getSigners().then(x => x[0]); +// console.log(`Deployer: ${deployer.address}`); +// +// const name = strategyContract.substring( +// strategyContract.lastIndexOf(":") + 1 +// ); +// const strategy = await ethers.getContractAt( +// strategyContract, +// stateObject[name].strategy +// ); +// const jar = await ethers.getContractAt(jarContract, stateObject[name].jar); +// const controller = await ethers.getContractAt( +// controllerContract, +// await jar.controller() +// ); +// const poolAddr = await jar.pool(); +// +// const token0 = await ethers.getContractAt( +// "src/lib/erc20.sol:ERC20", +// await strategy.token0() +// ); +// const token1 = await ethers.getContractAt( +// "src/lib/erc20.sol:ERC20", +// await strategy.token1() +// ); +// +// const t0Allowance = await token0.allowance(deployer.address, jar.address); +// const t1Allowance = await token1.allowance(deployer.address, jar.address); +// +// // Sanity checks +// if (t0Allowance.eq("0") || t1Allowance.eq("0")) { +// throw `❌❌ jar cannot spend tokens from deployer address!\ntoken0 allowance: ${t0Allowance.toString()}\ntoken1 allowance: ${t1Allowance.toString()}`; +// } +// if ((await controller.jars(poolAddr)) != jar.address) +// throw "❌❌ jar is not set on the controller!"; +// if ((await controller.strategies(poolAddr)) != strategy.address) +// throw "❌❌ strategy is not set on the controller!"; +// +// if (!stateObject[name].rebalanceTx) { +// // Initial position +// console.log("\nMinting initial position..."); +// const t0Balance = await token0.balanceOf(deployer.address); +// const t1Balance = await token1.balanceOf(deployer.address); +// try { +// if (!stateObject[name].initToken0DepositTx) { +// console.log("Sending token0 to the strategy..."); +// const initToken0DepositTx = await executeTx(callAttempts, () => +// token0.transfer( +// strategy.address, +// t0Balance.mul(BigNumber.from("20")).div(BigNumber.from(100)) +// ) +// ); +// if (initToken0DepositTx?.transactionHash) { +// stateObject[name].initToken0DepositTx = +// initToken0DepositTx.transactionHash; +// persistify(stateObject); +// } +// } +// if (!stateObject[name].initToken1DepositTx) { +// console.log("Sending token1 to the strategy..."); +// const initToken1DepositTx = await executeTx(callAttempts, () => +// token1.transfer( +// strategy.address, +// t1Balance.mul(BigNumber.from("20")).div(BigNumber.from(100)) +// ) +// ); +// if (initToken1DepositTx?.transactionHash) { +// stateObject[name].initToken1DepositTx = +// initToken1DepositTx.transactionHash; +// persistify(stateObject); +// } +// } +// } catch (error) { +// throw "❌❌ failed transfering one of the tokens to strategy!"; +// } +// console.log("Calling rebalance on the strategy..."); +// const rebalanceTx = await executeTx(callAttempts, () => +// strategy.rebalance() +// ); +// if (rebalanceTx?.transactionHash) { +// stateObject[name].rebalanceTx = rebalanceTx.transactionHash; +// persistify(stateObject); +// console.log( +// `✔️ Successfully minted initial position. Please double-check the tx hash ${rebalanceTx.transactionHash}` +// ); +// } else { +// throw `❌❌ Failed minting initial position!`; +// } +// } +// +// if (!stateObject[name].depositTx) { +// // Deposit Want +// console.log("\nDepositing in Jar..."); +// const t0Balance = await token0.balanceOf(deployer.address); +// const t1Balance = await token1.balanceOf(deployer.address); +// const depositTx = await executeTx(callAttempts, () => +// jar.deposit(t0Balance, t1Balance) +// ); +// if (depositTx?.transactionHash) { +// stateObject[name].depositTx = depositTx.transactionHash; +// persistify(stateObject); +// console.log( +// `✔️ Successfully deposited want in Jar. Please double-check the tx hash ${depositTx.transactionHash}` +// ); +// } else { +// throw `❌❌ Failed depositing want in Jar!`; +// } +// } +// +// if (!stateObject[name].harvestTx) { +// // Harvest +// const ratioBefore = await jar.getRatio(); +// console.log( +// `\nWaiting for ${timers.harvestV3 / 1000}s before harvesting...` +// ); +// await sleep(timers.harvestV3); +// const harvestTx = await executeTx(callAttempts, () => strategy.harvest()); +// +// await sleep(timers.tx); +// const ratioAfter = await jar.getRatio(); +// +// if (ratioAfter.gt(ratioBefore)) { +// console.log(`✔️ Harvest was successful`); +// stateObject[name].harvestTx = harvestTx.transactionHash; +// persistify(stateObject); +// } else { +// console.log(`❌❌ Harvest failed, ratio has not increased`); +// } +// } +// +// // Script Report +// const report = ` +// ${stateObject[name].name} - ${stateObject[name].harvestTx ? "Fully Tested" : "TEST FAILED!" +// } +// jar:\t${stateObject[name].jar} +// want:\t${stateObject[name].want} +// strategy:\t${stateObject[name].strategy} +// ---------------------------- +// `; +// console.log(report); +//}; + + +export const deployMiniChef = async() => { + const minichefContract = "src/optimism/minichefv2.sol:MiniChefV2"; + const pickleAddress = "0x0c5b4c92c948691EEBf185C17eeB9c230DC019E9"; + const minichefAddress = await flatDeployAndVerifyContract(minichefContract, [pickleAddress], false); + console.log(`MiniChefV2 Successfully deployed at ${minichefAddress}`); +} + +export const deployRewarder = async() => { + const rewarderContract = "src/optimism/PickleRewarder.sol:PickleRewarder"; + const opTokenAddress = "0x4200000000000000000000000000000000000042"; + const rewardPerSecond = 0; + const minichefAddress = "0x849C283375A156A6632E8eE928308Fcb61306b7B"; + const rewarderAddress = await flatDeployAndVerifyContract(rewarderContract, [opTokenAddress, rewardPerSecond, minichefAddress], false); + console.log(`Rewarder Successfully deployed at ${rewarderAddress}`); + +} diff --git a/scripts/deployer/utils.ts b/scripts/deployer/utils.ts new file mode 100644 index 000000000..ce6475cf2 --- /dev/null +++ b/scripts/deployer/utils.ts @@ -0,0 +1,194 @@ +import "@nomicfoundation/hardhat-toolbox"; +import {TransactionReceipt} from "@ethersproject/providers"; +import {ethers, run, config} from "hardhat"; +import "./flat"; +import {writeFileSync} from "fs"; +import {ConstructorArguments, OBJECT_FILE_NAME} from "./constants"; + +/** + * @param controllerContract example: "src/optimism/controller-v7.sol:ControllerV7" + */ +export const deployController = async ( + controllerContract: string, + governance: string, + strategist: string, + timelock: string, + devfund: string, + treasury: string +) => { + return flatDeployAndVerifyContract(controllerContract, [governance, strategist, timelock, devfund, treasury]); +}; + +/** + * @param {string} strategyContract example: "src/strategies/optimism/velodrome/strategy-velo-op-vlp.sol:StrategyVeloOpVlp" + */ +export const deployStrategy = async ( + strategyContract: string, + governance: string, + strategist: string, + controller: string, + timelock: string +) => { + return flatDeployAndVerifyContract(strategyContract, [governance, strategist, controller, timelock]); +}; + +/** + * @param {string} strategyContract example: "src/strategies/optimism/velodrome/strategy-velo-op-vlp.sol:StrategyVeloOpVlp" + * @param tickMultiplier multiplier for the tick range + */ +export const deployStrategyV3 = async ( + strategyContract: string, + tickMultiplier: number, + governance: string, + strategist: string, + controller: string, + timelock: string +) => { + return flatDeployAndVerifyContract(strategyContract, [tickMultiplier, governance, strategist, controller, timelock]); +}; + +/** + * Jar contracts only need to be verified once, every subsequent jar deployment gets verified automatically + * This function will not attempt verification, use `verifyJar()` for that + * @param {string} jarContract example: "src/pickle-jar.sol:PickleJar" + */ +export const deployJar = async ( + jarContract: string, + want: string, + governance: string, + timelock: string, + controller: string +) => { + return flatDeployAndVerifyContract(jarContract, [want, governance, timelock, controller], true, false); +}; + +/** + * Jar contracts only need to be verified once, every subsequent jar deployment gets verified automatically + * This function will not attempt verification, use `verifyJar()` for that + * @param {string} jarContract example: `src/optimism/pickle-jar-univ3.sol:PickleJarUniV3Optimism` + */ +export const deployJarV3 = async ( + jarContract: string, + jarName: string, + jarSymbol: string, + pool: string, + native: string, + governance: string, + timelock: string, + controller: string +) => { + return flatDeployAndVerifyContract( + jarContract, + [jarName, jarSymbol, pool, native, governance, timelock, controller], + true, + false + ); +}; + +/** + * @param jarFlattenedContract use the flattened contract path. example: `src/tmp/pickle-jar.sol:PickleJar` + */ +export const verifyJar = async ( + jarFlattenedContract: string, + jarAddr: string, + want: string, + governance: string, + timelock: string, + controller: string +) => { + return verifyContract(jarFlattenedContract, jarAddr, [want, governance, timelock, controller]); +}; + +/** + * @param strategyFlattenedContract use the flattened contract path. example `src/tmp/strategy-velo-op-vlp.sol:StrategyVeloOpVlp` + */ +export const verifyStrategy = async ( + strategyFlattenedContract: string, + strategyAddr: string, + governance: string, + strategist: string, + controller: string, + timelock: string +) => { + return verifyContract(strategyFlattenedContract, strategyAddr, [governance, strategist, controller, timelock]); +}; + +export const flatDeployAndVerifyContract = async ( + contract: string, + constructorArguments: ConstructorArguments, + flatten: boolean = true, + verify: boolean = true +) => { + // Flatten + const flattened = flatten ? await flattenContract(contract) : contract; + + // Deploy + const receipt = await deployContract(flattened, constructorArguments); + const contractName = flattened.substring(flattened.lastIndexOf(":" + 1)); + + // Verify + if (verify) { + const verificationSuccessfull = await verifyContract(flattened, receipt.contractAddress, constructorArguments); + if (!verificationSuccessfull) + console.log("❌❌ Verification failed!\n\tContract: " + contractName + "\n\tAddress: " + receipt.contractAddress); + } + return receipt.contractAddress; +}; + +export const deployContract = async ( + contract: string, + constructorArguments: ConstructorArguments +): Promise => { + const contractFactory = await ethers.getContractFactory(contract); + const txn = await contractFactory.deploy(...constructorArguments); + return txn.deployTransaction.wait(); +}; + +export const flattenContract = async (contract: string) => { + const path = contract.substring(0, contract.lastIndexOf(":")); + const fileName = path.substring(path.lastIndexOf("/")); + const contractName = contract.substring(contract.lastIndexOf(":")); + await run("flat", { + files: [path], + output: "src/tmp" + fileName, + }); + + // Compile the flattened contract + // Limit Hardhat to only compile the flattened contracts path + const oldSourcesPath = config.paths.sources; + config.paths.sources = "src/tmp"; + await run("compile"); + config.paths.sources = oldSourcesPath; + const flattenedContract = "src/tmp" + fileName + contractName; + + return flattenedContract; +}; + +export const verifyContract = async ( + contract: string, + address: string, + constructorArguments: ConstructorArguments +): Promise => { + try { + await sleep(10); // wait for etherscan to index the new contract + await run("verify:verify", { + contract: contract, + address: address, + constructorArguments: constructorArguments, + }); + return true; + } catch (e) { + console.error(e); + return false; + } +}; + +export const sleep = async (seconds: number) => { + console.log(`Sleeping for ${seconds}s...`); + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); +}; + +export const persistify = (deploymentStateObject) => { + const stringified = JSON.stringify(deploymentStateObject, null, 4); + writeFileSync(__dirname.concat("/").concat(OBJECT_FILE_NAME), stringified); +}; diff --git a/scripts/deployer/v3Migrations.ts b/scripts/deployer/v3Migrations.ts new file mode 100644 index 000000000..0528981a9 --- /dev/null +++ b/scripts/deployer/v3Migrations.ts @@ -0,0 +1,558 @@ +import "@nomicfoundation/hardhat-toolbox"; +import {deployJarV3, deployStrategyV3, flatDeployAndVerifyContract} from "./utils"; +import fetch from "cross-fetch"; +import * as dotenv from "dotenv"; +import {BigNumber, Contract} from "ethers"; +import {ethers} from "hardhat"; +dotenv.config(); + +interface IData { + [chain: string]: { + strats: {oldJarAddr: string; oldStrategyAddr: string; newStrategyContract: string}[]; + keeper: string; + api: string; + migrationProxy?: string; + }; +} +const data: IData = { + ETH: { + strats: [ + { + oldJarAddr: "0xAaCDaAad9a9425bE2d666d08F741bE4F081C7ab1", + oldStrategyAddr: "0xae2e6daA0FD5c098C8cE87Df573E32C9d6493384", //wbtc-eth + newStrategyContract: "src/strategies/uniswapv3/strategy-univ3-wbtc-eth-lp.sol:StrategyWbtcEthUniV3", + }, + { + oldJarAddr: "0xf0Fb82757B9f8A3A3AE3524e385E2E9039633948", + oldStrategyAddr: "0x3B63E25e9fD76F152b4a2b6DfBfC402c5ba19A01", //eth-cow + newStrategyContract: "src/strategies/uniswapv3/strategy-univ3-eth-cow-lp.sol:StrategyEthCowUniV3", + }, + { + oldJarAddr: "0x49ED0e6B438430CEEdDa8C6d06B6A2797aFA81cA", + oldStrategyAddr: "0x5e20293615A4Caa3E2a9B5D24B40DBB176Ec01a8", + newStrategyContract: "src/strategies/uniswapv3/strategy-univ3-ape-eth-lp.sol:StrategyApeEthUniV3", + }, + { + oldJarAddr: "0x8CA1D047541FE183aE7b5d80766eC6d5cEeb942A", + oldStrategyAddr: "0xd33d3D71C6F710fb7A94469ED958123Ab86858b1", + newStrategyContract: "src/strategies/uniswapv3/strategy-univ3-usdc-eth-05-lp.sol:StrategyUsdcEth05UniV3", + }, + ], + keeper: "0xEb088Cb6B5EDec8BF4Ec1189b28521EE820686BF", + api: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY_MAINNET}`, + }, + OP: { + strats: [ + { + oldJarAddr: "0xc335740c951F45200b38C5Ca84F0A9663b51AEC6", + oldStrategyAddr: "0x754ece9AC6b3FF9aCc311261EC82Bd1B69b8E00B", + newStrategyContract: + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-btc-lp.sol:StrategyEthBtcUniV3Optimism", + }, + { + oldJarAddr: "0xbE27C2415497f8ae5E6103044f460991E32636F8", + oldStrategyAddr: "0xE9936818ecd2a6930407a11C090260b5390A954d", + newStrategyContract: + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-dai-lp.sol:StrategyEthDaiUniV3Optimism", + }, + { + oldJarAddr: "0x24f8b36b7349053A33E3767bc44B8FF20813AE5e", + oldStrategyAddr: "0x1634e17813D54Ffc7506523D6e8bf08556207468", + newStrategyContract: + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-op-lp.sol:StrategyEthOpUniV3Optimism", + }, + { + oldJarAddr: "0xBBF8233867c1982D66EA920d726d24391B713550", + oldStrategyAddr: "0x1570B5D17a0796112263F4E3FAeee53459B41A49", + newStrategyContract: + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-usdc-lp.sol:StrategyEthUsdcUniV3Optimism", + }, + { + oldJarAddr: "0x37Cc6Ce6eda683AB97433f4Bf26bAbD63889df23", + oldStrategyAddr: "0x1Bb40496D3074A2345d5e3Ac28b990854A7BDe34", + newStrategyContract: + "src/strategies/optimism/uniswapv3/strategy-univ3-susd-dai-lp.sol:StrategySusdDaiUniV3Optimism", + }, + { + oldJarAddr: "0x637Bbfa0Ba3dE1341c469B15986D4AaE2c8d3cE5", + oldStrategyAddr: "0xa99e8a5754a53bE312Fba259c7C4619cfB00E849", + newStrategyContract: + "src/strategies/optimism/uniswapv3/strategy-univ3-susd-usdc-lp.sol:StrategySusdUsdcUniV3Optimism", + }, + { + oldJarAddr: "0xae2A28B97FFF55ca62881cBB30De0A3D9949F234", + oldStrategyAddr: "0x387C985176A314c9e5D927a99724de98576812aF", + newStrategyContract: + "src/strategies/optimism/uniswapv3/strategy-univ3-usdc-dai-lp.sol:StrategyUsdcDaiUniV3Optimism", + }, + ], + keeper: "0xA1F13ccC3205F767cEa4F254Bb1A2B53933798b2", + api: `https://opt-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY_OPTIMISM}`, + }, + POLY: { + strats: [ + { + oldJarAddr: "0xf4b1635f6B71D7859B4184EbDB5cf7321e828055", + oldStrategyAddr: "0xbE27C2415497f8ae5E6103044f460991E32636F8", + newStrategyContract: "src/strategies/polygon/uniswapv3/strategy-univ3-wbtc-eth-lp.sol:StrategyWbtcEthUniV3Poly", + }, + { + oldJarAddr: "0x925b6f866AeB88131d159Fc790b9FC8203621B3C", + oldStrategyAddr: "0x11b8c80F452e54ae3AB2E8ce9eF9603B0a0f56D9", + newStrategyContract: + "src/strategies/polygon/uniswapv3/strategy-univ3-matic-eth-lp.sol:StrategyMaticEthUniV3Poly", + }, + { + oldJarAddr: "0x09e4E5fc62d8ae06fD44b3527235693f29fda852", + oldStrategyAddr: "0x293731CA8Da0cf1d6dfFB5125943F05Fe0B5fF99", + newStrategyContract: + "src/strategies/polygon/uniswapv3/strategy-univ3-matic-usdc-lp.sol:StrategyMaticUsdcUniV3Poly", + }, + { + oldJarAddr: "0x75415BF29f054Ab9047D26501Ad5ef93B5364eb0", + oldStrategyAddr: "0xD5236f71580E951010E814118075F2Dda90254db", + newStrategyContract: "src/strategies/polygon/uniswapv3/strategy-univ3-usdc-eth-lp.sol:StrategyUsdcEthUniV3Poly", + }, + { + oldJarAddr: "0x6ddCE484E929b2667C604f6867A4a7b3d344A917", + oldStrategyAddr: "0x846d0ED75c285E6D70A925e37581D0bFf94c7651", + newStrategyContract: + "src/strategies/polygon/uniswapv3/strategy-univ3-usdc-usdt-lp.sol:StrategyUsdcUsdtUniV3Poly", + }, + ], + keeper: "0xa5F338D9a684A75C47a2caF3b104A496ec8bEad0", + api: `https://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY_POLYGON}`, + }, + ARB: { + strats: [ + { + oldJarAddr: "0x1212DdD66C8eB227183fce794C4c13d1c5a87b88", + oldStrategyAddr: "0x41A610baad8BfdB620Badff488A034B06B13790D", //eth-usdc + newStrategyContract: + "src/strategies/arbitrum/uniswapv3/strategy-univ3-eth-usdc-lp.sol:StrategyUsdcEthUniV3Arbi", + }, + { + oldJarAddr: "0xe5BD4954Bd6749a8E939043eEDCe4C62b41CC6D0", + oldStrategyAddr: "0x9C485ae43280dD0375C8c2290F1f77aee17CF512", //eth-gmx + newStrategyContract: "src/strategies/arbitrum/uniswapv3/strategy-univ3-gmx-eth-lp.sol:StrategyGmxEthUniV3Arbi", + }, + ], + keeper: "0xaEE8A262E3A4E89AF95BEF5F224C74159d6Fdf4d", + api: `https://arb-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY_ARBITRUM}`, + }, +}; +const tsukeAddr = "0x0f571D2625b503BB7C1d2b5655b483a2Fa696fEf"; + +const jarSnapshot = async (jarAddr: string, chain: string) => { + const api = data[chain].api; + let stakingAddress: string; + if (chain === "ETH") { + const provider = new ethers.providers.JsonRpcProvider(api); + const abi = ["function getGauge(address) view returns(address)"]; + const gaugeProxy = new ethers.Contract("0x2e57627ACf6c1812F99e274d0ac61B786c19E74f", abi, provider); + stakingAddress = await gaugeProxy.getGauge(jarAddr); + } + const aggregatedTransfers = []; + + // Get transfers logs paginated in 1k transfers per batch + // Alchemy rounds the values on this endpoint, so we can't use it. + let keepPulling = true; + let pageKey: string | undefined = undefined; + while (keepPulling) { + const options = { + method: "POST", + headers: {accept: "application/json", "content-type": "application/json"}, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "alchemy_getAssetTransfers", + params: [ + { + fromBlock: "0x0", + toBlock: "latest", + contractAddresses: [jarAddr], + category: ["erc20"], + withMetadata: false, + excludeZeroValue: true, + maxCount: "0x3e8", // 1000 transfers per response + }, + ], + }), + }; + if (pageKey) + options.body = JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "alchemy_getAssetTransfers", + params: [ + { + fromBlock: "0x0", + toBlock: "latest", + contractAddresses: [jarAddr], + category: ["erc20"], + withMetadata: false, + excludeZeroValue: true, + maxCount: "0x3e8", // 1000 transfers per response + pageKey, + }, + ], + }); + + const resp = await (await fetch(api, options)).json(); + if (resp.result.pageKey) { + pageKey = resp.result.pageKey; + keepPulling = true; + } else { + keepPulling = false; + } + aggregatedTransfers.push(resp.result.transfers); + } + + // Get the actual transfers logs + const transfers: { + blockHash: string; + blockNumber: string; + transactionIndex: string; + address: string; + logIndex: string; + data: string; + removed: boolean; + topics: string[]; + transactionHash: string; + }[] = []; + for (let i = 0; i < aggregatedTransfers.length; i++) { + const transfersBatch = aggregatedTransfers[i]; + const topics = ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]; //transfer log signature + const fromBlock = transfersBatch[0].blockNum; + const toBlock = transfersBatch[transfersBatch.length - 1].blockNum; + + const options = { + method: "POST", + headers: {accept: "application/json", "content-type": "application/json"}, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "eth_getLogs", + params: [ + { + address: [jarAddr], + fromBlock, + toBlock, + topics, + }, + ], + }), + }; + + const resp = await (await fetch(api, options)).json(); + transfers.push(...resp.result); + } + + // Decode transfer logs + const users: {[user: string]: BigNumber} = {}; + transfers.forEach((transfer) => { + const from: string = ethers.utils.defaultAbiCoder.decode(["address"], transfer.topics[1])[0]; + const to: string = ethers.utils.defaultAbiCoder.decode(["address"], transfer.topics[2])[0]; + const value: BigNumber = ethers.utils.defaultAbiCoder.decode(["uint256"], transfer.data)[0]; + + if (from === stakingAddress || to === stakingAddress) return; + + if (from === ethers.constants.AddressZero) { + const toBalance = users[to]; + users[to] = toBalance ? toBalance.add(value) : value; + } else if (to === ethers.constants.AddressZero) { + const fromBalance = users[from]; + if (!fromBalance || fromBalance.sub(value).lt(BigNumber.from("0"))) { + console.log(transfer); + console.log("Balance before transfer: " + fromBalance); + throw "Address transferring more than it owns!"; + } + users[from] = fromBalance.sub(value); + } else { + const fromBalance = users[from]; + const toBalance = users[to]; + if (!fromBalance || fromBalance.sub(value).lt(BigNumber.from("0"))) { + console.log(transfer); + console.log("Balance before transfer: " + fromBalance); + throw "Address transferring more than it owns!"; + } + users[from] = fromBalance.sub(value); + users[to] = toBalance ? toBalance.add(value) : value; + } + }); + + // Cleanup users with 0 balance + Object.keys(users).forEach((userAddr) => users[userAddr].isZero() && delete users[userAddr]); + + Object.keys(users).forEach((user) => console.log(user + ": " + users[user].toString())); + + return users; +}; + +const deployMigrationProxy = async () => { + const proxyContract = "src/tmp/univ3-migration-proxy.sol:Proxy"; + const proxyAddr = await flatDeployAndVerifyContract(proxyContract, [], false, true); + const migrationProxy = await ethers.getContractAt(proxyContract, proxyAddr); + console.log(`✔️ Migration Proxy deployed at: ${migrationProxy.address}`); + return migrationProxy.address; +}; + +// Call execute on a strategy to migration proxy +const callExecuteToProxy = async ( + executeContract: Contract, + proxyContract: Contract, + targetFuncName: string, + targetFuncArgs: any[] +) => { + const txnData = proxyContract.interface.encodeFunctionData(targetFuncName, targetFuncArgs); + return await executeContract.execute(proxyContract.address, txnData).then((x) => x.wait()); +}; + +const migrateV3StratAndJar1 = async (oldStrategyAddress: string, newStrategyContract: string, chain: string) => { + const deployer = await ethers.getSigners().then((x) => x[0]); + console.log(`Deployer: ${deployer.address}`); + + const poolAbi = ["function tickSpacing() view returns(int24)", "function fee() view returns(uint24)"]; + + const oldStrategy = await ethers.getContractAt(newStrategyContract, oldStrategyAddress); + const controller = await ethers.getContractAt( + "src/optimism/controller-v7.sol:ControllerV7", + await oldStrategy.controller() + ); + const pool = await ethers.getContractAt(poolAbi, await oldStrategy.pool()); + + // Calculate oldStrategy tickRangeMultiplier + const tickSpacing = await pool.tickSpacing(); + const utick = await oldStrategy.tick_upper(); + const ltick = await oldStrategy.tick_lower(); + const tickRangeMultiplier = (utick - ltick) / 2 / tickSpacing; + + // Deploy the new strategy + const strategyAddr = await deployStrategyV3( + newStrategyContract, + tickRangeMultiplier, + deployer.address, + deployer.address, + controller.address, + deployer.address + ); + const strategy = await ethers.getContractAt(newStrategyContract, strategyAddr); + console.log(`✔️ New Strategy deployed at: ${strategy.address}`); + + // Deploy the new jar + const native = await ethers.getContractAt("src/lib/erc20.sol:ERC20", await strategy.native()); + const token0 = await ethers.getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + const token1 = await ethers.getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + const jarContract = "src/optimism/pickle-jar-univ3.sol:PickleJarUniV3"; + const jarName = `pickling ${await token0.symbol()}/${await token1.symbol()} Jar`; + const jarSymbol = `p${await token0.symbol()}${await token1.symbol()}`; + const jarAddr = await deployJarV3( + jarContract, + jarName, + jarSymbol, + pool.address, + native.address, + deployer.address, + deployer.address, + controller.address + ); + const jar = await ethers.getContractAt(jarContract, jarAddr); + console.log(`✔️ New PickleJarV3 deployed at: ${jar.address}`); + + // Deploy migration proxy if not deployed before + const proxyContract = "src/tmp/univ3-migration-proxy.sol:Proxy"; + let proxyAddr = data[chain].migrationProxy; + if (!data[chain].migrationProxy) { + proxyAddr = await deployMigrationProxy(); + console.log("Please update proxy address for " + chain + " chain."); + } + const migrationProxy = await ethers.getContractAt(proxyContract, proxyAddr); + + // Transfer Liquidity position from oldStrategy to deployer + const tokenId = await oldStrategy.tokenId(); + const nftManAddr = await oldStrategy.nftManager(); + const nftManAbi = [ + "function transferFrom(address from, address to, uint256 tokenId)", + "function ownerOf(uint256) view returns(address)", + "function WETH9() view returns(address)", + "function decreaseLiquidity(tuple(uint256 tokenId, uint128 liquidity, uint256 amount0Min, uint256 amount1Min, uint256 deadline)) payable returns(uint256 amount0, uint256 amount1)", + "function collect(tuple(uint256 tokenId, address recipient, uint128 amount0Max, uint128 amount1Max)) payable returns(uint256 amount0, uint256 amount1)", + "function positions(uint256) 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)", + ]; + const nftManContract = await ethers.getContractAt(nftManAbi, nftManAddr); + + await callExecuteToProxy(oldStrategy, migrationProxy, "transferFrom", [ + nftManAddr, + oldStrategy.address, + deployer.address, + tokenId, + ]); + + // Transfer oldStrategy dust to deployer + const badStratBal0: BigNumber = await token0.balanceOf(oldStrategy.address); + const badStratBal1: BigNumber = await token1.balanceOf(oldStrategy.address); + if (!badStratBal0.isZero()) { + await callExecuteToProxy(oldStrategy, migrationProxy, "transfer", [token0.address, deployer.address, badStratBal0]); + } + if (!badStratBal1.isZero()) { + await callExecuteToProxy(oldStrategy, migrationProxy, "transfer", [token1.address, deployer.address, badStratBal1]); + } + + // Remove all liquidity from the NFT, then send NFT back to oldStrategy + const {liquidity} = await nftManContract.positions(tokenId); + const deadline = Math.floor(Date.now() / 1000) + 300; + const [amount0, amount1] = await nftManContract.callStatic.decreaseLiquidity([tokenId, liquidity, 0, 0, deadline]); + await nftManContract.decreaseLiquidity([tokenId, liquidity, 0, 0, deadline]); + await nftManContract.collect([tokenId, deployer.address, amount0.mul(2), amount1.mul(2)]); + await nftManContract.transferFrom(deployer.address, oldStrategy.address, tokenId); + + // Remove old strategy from the keeper watch-list + const keeperAbi = ["function removeStrategy(address)", "function addStrategies(address[])"]; + const keeper = await ethers.getContractAt(keeperAbi, data[chain].keeper); + await keeper.removeStrategy(oldStrategy.address); + + console.log( + "First part of the migration is successful. Please call setJar, approveStrategy and setStrategy on the controller before proceeding with the next part." + ); +}; + +const migrateV3StratAndJar2 = async ( + newJarAddress: string, + newStrategyAddress: string, + newStrategyContract: string, + chain: string +) => { + const deployer = await ethers.getSigners().then((x) => x[0]); + console.log(`Deployer: ${deployer.address}`); + + const strategy = await ethers.getContractAt(newStrategyAddress, newStrategyContract); + const jarContract = "src/optimism/pickle-jar-univ3.sol:PickleJarUniV3"; + const jar = await ethers.getContractAt(newJarAddress, jarContract); + const controller = await ethers.getContractAt( + "src/optimism/controller-v7.sol:ControllerV7", + await strategy.controller() + ); + const token0 = await ethers.getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + const token1 = await ethers.getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + const poolAddr = await strategy.pool(); + + // Ensure the new strategy is set on the controller + const isStrategySet = (await controller.strategies(poolAddr)) === strategy.address; + const isJarSet = (await controller.jars(poolAddr)) === jar.address; + if (!isStrategySet || !isJarSet) { + console.log("Strategy or jar is not set on the controller. Approve and set the new strategy and jar first!"); + return; + } + + // Mint initial position on the new strategy + let deployerBalance0 = await token0.balanceOf(deployer.address); + let deployerBalance1 = await token1.balanceOf(deployer.address); + await token0.transfer(strategy.address, deployerBalance0.div(10)); + await token1.transfer(strategy.address, deployerBalance1.div(10)); + await strategy.rebalance(); + + // Deposit all tokens in the jar + deployerBalance0 = await token0.balanceOf(deployer.address); + deployerBalance1 = await token1.balanceOf(deployer.address); + await token0.approve(jar.address, ethers.constants.MaxUint256); + await token1.approve(jar.address, ethers.constants.MaxUint256); + await jar.deposit(deployerBalance0, deployerBalance1); + + // Transfer refunded tokens to the strategy and call rebalance + deployerBalance0 = await token0.balanceOf(deployer.address); + deployerBalance1 = await token1.balanceOf(deployer.address); + await token0.transfer(strategy.address, deployerBalance0); + await token1.transfer(strategy.address, deployerBalance1); + await strategy.rebalance(); + + // Whitelist harvesters + await strategy.whitelistHarvesters([tsukeAddr, data[chain].keeper]); + + // Add new strategy to keeper. Remove old one + const keeperAbi = ["function removeStrategy(address)", "function addStrategies(address[])"]; + const keeper = await ethers.getContractAt(keeperAbi, data[chain].keeper); + await keeper.addStrategies([strategy.address]); +}; + +const distributeJarTokensToUsers = async (oldJarAddr: string, newJarAddr: string, chain: string) => { + const deployer = await ethers.getSigners().then((x) => x[0]); + console.log(`Deployer: ${deployer.address}`); + + const jarContract = "src/optimism/pickle-jar-univ3.sol:PickleJarUniV3"; + const jar = await ethers.getContractAt(newJarAddr, jarContract); + + const deployerPTokenBalance: BigNumber = await jar.balanceOf(deployer.address); + const usersOldBalances = await jarSnapshot(oldJarAddr, chain); + const totalOldBalances = Object.keys(usersOldBalances).reduce( + (acc, cur) => acc.add(usersOldBalances[cur]), + BigNumber.from(0) + ); + + const usersNewBalances = {}; + Object.keys(usersOldBalances).forEach((userAddr) => { + const oldBalance = usersOldBalances[userAddr]; + const newBalance = oldBalance.mul(deployerPTokenBalance).div(totalOldBalances); + usersNewBalances[userAddr] = newBalance; + }); + + const totalNewBalances = Object.keys(usersNewBalances).reduce( + (acc, cur) => acc.add(usersNewBalances[cur]), + BigNumber.from(0) + ); + console.log("Deployer pToken Balance: " + deployerPTokenBalance.toString()); + console.log("pTokens to be dispersed: " + totalNewBalances.toString()); + if (totalNewBalances.gt(deployerPTokenBalance)) { + console.log("Users' total new balances is more than what the deployer have!"); + return; + } + + const usersAddresses = []; + const usersShares = []; + Object.keys(usersNewBalances).forEach((userAddr) => { + usersAddresses.push(userAddr); + usersShares.push(usersNewBalances[userAddr]); + }); + + const proxyContract = "src/tmp/univ3-migration-proxy.sol:Proxy"; + let proxyAddr = data[chain].migrationProxy; + if (!data[chain].migrationProxy) { + console.log("Please update migration proxy address for " + chain + " chain."); + return; + } + const migrationProxy = await ethers.getContractAt(proxyContract, proxyAddr); + + await jar.approve(migrationProxy.address, deployerPTokenBalance); + await migrationProxy.multisend(jar.address, usersAddresses, usersShares); +}; + +// Functions +const main = async () => { + // Notes: + // 1) Pause the old jar. + // 2) Transfer governance & timelock of the oldStrategy to deployer. + // 3) Run migrateV3StratAndJar1(). + // 4) Set the new jar on the controller. + // 5) Approve & set the new strategy on controller. + // 6) Run migrateV3StratAndJar2(). + // 7) Run distributeJarTokensToUsers(). + + const chain = "ARB"; + const strategyData = data[chain].strats[0]; + const newJarAddr = ""; + const newStrategyAddr = ""; + try { + await migrateV3StratAndJar1(strategyData.oldStrategyAddr, strategyData.newStrategyContract, chain); + await migrateV3StratAndJar2(newJarAddr, newStrategyAddr, strategyData.newStrategyContract, chain); + await distributeJarTokensToUsers("", newJarAddr, chain); + } catch (error) { + console.log("Something went wrong!"); + console.log("Error:\n" + error); + } +}; + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/src/arbitrum/interfaces/univ3/IERC165.sol b/src/arbitrum/interfaces/univ3/IERC165.sol deleted file mode 100644 index d7d8dadda..000000000 --- a/src/arbitrum/interfaces/univ3/IERC165.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity >=0.6.12; - -/** - * @dev Interface of the ERC165 standard, as defined in the - * https://eips.ethereum.org/EIPS/eip-165[EIP]. - * - * Implementers can declare support of contract interfaces, which can then be - * queried by others ({ERC165Checker}). - * - * For an implementation, see {ERC165}. - */ -interface IERC165 { - /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] - * to learn more about how these ids are created. - * - * This function call must use less than 30 000 gas. - */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} diff --git a/src/arbitrum/interfaces/univ3/IERC721.sol b/src/arbitrum/interfaces/univ3/IERC721.sol deleted file mode 100644 index acbc7a8a5..000000000 --- a/src/arbitrum/interfaces/univ3/IERC721.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity >=0.6.12; - -import "./IERC165.sol"; - -/** - * @dev Required interface of an ERC721 compliant contract. - */ -interface IERC721 is IERC165 { - /** - * @dev Emitted when `tokenId` token is transferred from `from` to `to`. - */ - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenId - ); - - /** - * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. - */ - event Approval( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - /** - * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. - */ - event ApprovalForAll( - address indexed owner, - address indexed operator, - bool approved - ); - - /** - * @dev Returns the number of tokens in ``owner``'s account. - */ - function balanceOf(address owner) external view returns (uint256 balance); - - /** - * @dev Returns the owner of the `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function ownerOf(uint256 tokenId) external view returns (address owner); - - /** - * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients - * are aware of the ERC721 protocol to prevent tokens from being forever locked. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * - * Emits a {Transfer} event. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) external; - - /** - * @dev Transfers `tokenId` token from `from` to `to`. - * - * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. - * - * Emits a {Transfer} event. - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) external; - - /** - * @dev Gives permission to `to` to transfer `tokenId` token to another account. - * The approval is cleared when the token is transferred. - * - * Only a single account can be approved at a time, so approving the zero address clears previous approvals. - * - * Requirements: - * - * - The caller must own the token or be an approved operator. - * - `tokenId` must exist. - * - * Emits an {Approval} event. - */ - function approve(address to, uint256 tokenId) external; - - /** - * @dev Returns the account approved for `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function getApproved(uint256 tokenId) - external - view - returns (address operator); - - /** - * @dev Approve or remove `operator` as an operator for the caller. - * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. - * - * Requirements: - * - * - The `operator` cannot be the caller. - * - * Emits an {ApprovalForAll} event. - */ - function setApprovalForAll(address operator, bool _approved) external; - - /** - * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. - * - * See {setApprovalForAll} - */ - function isApprovedForAll(address owner, address operator) - external - view - returns (bool); - - /** - * @dev Safely transfers `tokenId` token from `from` to `to`. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * - * Emits a {Transfer} event. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes calldata data - ) external; -} diff --git a/src/arbitrum/interfaces/univ3/ISwapRouter.sol b/src/arbitrum/interfaces/univ3/ISwapRouter.sol deleted file mode 100644 index edc10473c..000000000 --- a/src/arbitrum/interfaces/univ3/ISwapRouter.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.7; -pragma experimental ABIEncoderV2; - -interface ISwapRouter { - struct ExactInputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; - address recipient; - uint256 amountIn; - uint256 amountOutMinimum; - uint160 sqrtPriceLimitX96; - } - - struct ExactInputParams { - bytes path; - address recipient; - uint256 deadline; - uint256 amountIn; - uint256 amountOutMinimum; - } - - function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); - - function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); -} - -interface IUniswapV3Factory { - function getPool( - address tokenA, - address tokenB, - uint24 fee - ) external view returns (address pool); -} diff --git a/src/arbitrum/interfaces/univ3/ISwapRouter02.sol b/src/arbitrum/interfaces/univ3/ISwapRouter02.sol deleted file mode 100644 index e42c5716e..000000000 --- a/src/arbitrum/interfaces/univ3/ISwapRouter02.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.6.12; -pragma experimental ABIEncoderV2; - -/// @title Router token swapping functionality -/// @notice Functions for swapping tokens via Uniswap V3 -interface ISwapRouter02 { - struct ExactInputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; - address recipient; - uint256 amountIn; - uint256 amountOutMinimum; - uint160 sqrtPriceLimitX96; - } - - /// @notice Swaps `amountIn` of one token for as much as possible of another token - /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, - /// and swap the entire amount, enabling contracts to send tokens before calling this function. - /// @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 amountIn; - uint256 amountOutMinimum; - } - - /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path - /// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance, - /// and swap the entire amount, enabling contracts to send tokens before calling this function. - /// @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 amountOut; - uint256 amountInMaximum; - uint160 sqrtPriceLimitX96; - } - - /// @notice Swaps as little as possible of one token for `amountOut` of another token - /// that may remain in the router after the swap. - /// @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 amountOut; - uint256 amountInMaximum; - } - - /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) - /// that may remain in the router after the swap. - /// @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/arbitrum/interfaces/univ3/IUniswapV3Pool.sol b/src/arbitrum/interfaces/univ3/IUniswapV3Pool.sol deleted file mode 100644 index 87806faf9..000000000 --- a/src/arbitrum/interfaces/univ3/IUniswapV3Pool.sol +++ /dev/null @@ -1,20 +0,0 @@ -// 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"; - -/// @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 -{ - function fee() external view returns (uint24); -} diff --git a/src/arbitrum/interfaces/univ3/IUniswapV3PositionsNFT.sol b/src/arbitrum/interfaces/univ3/IUniswapV3PositionsNFT.sol deleted file mode 100644 index 3c18e508f..000000000 --- a/src/arbitrum/interfaces/univ3/IUniswapV3PositionsNFT.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.6.12; -pragma experimental ABIEncoderV2; - -import "./IERC721.sol"; - -// Originally INonfungiblePositionManager -interface IUniswapV3PositionsNFT is IERC721 { - struct CollectParams { - uint256 tokenId; - address recipient; - uint128 amount0Max; - uint128 amount1Max; - } - - struct MintParams { - address token0; - address token1; - uint24 fee; - int24 tickLower; - int24 tickUpper; - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0Min; - uint256 amount1Min; - address recipient; - uint256 deadline; - } - - struct IncreaseLiquidityParams { - uint256 tokenId; - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0Min; - uint256 amount1Min; - uint256 deadline; - } - - /// @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, // [0] - address operator, // [1] - address token0, // [2] - address token1, // [3] - uint24 fee, // [4] - int24 tickLower, // [5] - int24 tickUpper, // [6] - uint128 liquidity, // [7] - uint256 feeGrowthInside0LastX128, // [8] - uint256 feeGrowthInside1LastX128, // [9] - uint128 tokensOwed0, // [10] - uint128 tokensOwed1 // [11] - ); - - 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); - - /// @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); - - function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); - - function mint(MintParams calldata params) - external - payable - returns ( - uint256 tokenId, - uint128 liquidity, - uint256 amount0, - uint256 amount1 - ); - - function burn(uint256 tokenId) external payable; - - function refundETH() external payable; - - function unwrapWETH9(uint256 amountMinimum, address recipient) external payable; - - function sweepToken( - address token, - uint256 amountMinimum, - address recipient - ) external payable; -} diff --git a/src/arbitrum/interfaces/univ3/IUniswapV3Staker.sol b/src/arbitrum/interfaces/univ3/IUniswapV3Staker.sol deleted file mode 100644 index 2163de6cc..000000000 --- a/src/arbitrum/interfaces/univ3/IUniswapV3Staker.sol +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.6.12; -pragma experimental ABIEncoderV2; - -import "./IUniswapV3Pool.sol"; - -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); -} - -/// @title Uniswap V3 Staker Interface -/// @notice Allows staking nonfungible liquidity tokens in exchange for reward tokens -interface IUniswapV3Staker { - /// @param rewardToken The token being distributed as a reward - /// @param pool The Uniswap V3 pool - /// @param startTime The time when the incentive program begins - /// @param endTime The time when rewards stop accruing - /// @param refundee The address which receives any remaining reward tokens when the incentive is ended - struct IncentiveKey { - IERC20Minimal rewardToken; - IUniswapV3Pool pool; - uint256 startTime; - uint256 endTime; - address refundee; - } - - /// @notice The max duration of an incentive in seconds - function maxIncentiveDuration() external view returns (uint256); - - /// @notice The max amount of seconds into the future the incentive startTime can be set - function maxIncentiveStartLeadTime() external view returns (uint256); - - /// @notice Represents a staking incentive - /// @param incentiveId The ID of the incentive computed from its parameters - /// @return totalRewardUnclaimed The amount of reward token not yet claimed by users - /// @return totalSecondsClaimedX128 Total liquidity-seconds claimed, represented as a UQ32.128 - /// @return numberOfStakes The count of deposits that are currently staked for the incentive - function incentives(bytes32 incentiveId) - external - view - returns ( - uint256 totalRewardUnclaimed, - uint160 totalSecondsClaimedX128, - uint96 numberOfStakes - ); - - /// @notice Returns information about a deposited NFT - /// @return owner The owner of the deposited NFT - /// @return numberOfStakes Counter of how many incentives for which the liquidity is staked - /// @return tickLower The lower tick of the range - /// @return tickUpper The upper tick of the range - function deposits(uint256 tokenId) - external - view - returns ( - address owner, - uint48 numberOfStakes, - int24 tickLower, - int24 tickUpper - ); - - /// @notice Returns information about a staked liquidity NFT - /// @param tokenId The ID of the staked token - /// @param incentiveId The ID of the incentive for which the token is staked - /// @return secondsPerLiquidityInsideInitialX128 secondsPerLiquidity represented as a UQ32.128 - /// @return liquidity The amount of liquidity in the NFT as of the last time the rewards were computed - function stakes(uint256 tokenId, bytes32 incentiveId) - external - view - returns (uint160 secondsPerLiquidityInsideInitialX128, uint128 liquidity); - - /// @notice Returns amounts of reward tokens owed to a given address according to the last time all stakes were updated - /// @param rewardToken The token for which to check rewards - /// @param owner The owner for which the rewards owed are checked - /// @return rewardsOwed The amount of the reward token claimable by the owner - function rewards(IERC20Minimal rewardToken, address owner) external view returns (uint256 rewardsOwed); - - /// @notice Creates a new liquidity mining incentive program - /// @param key Details of the incentive to create - /// @param reward The amount of reward tokens to be distributed - function createIncentive(IncentiveKey memory key, uint256 reward) external; - - /// @notice Ends an incentive after the incentive end time has passed and all stakes have been withdrawn - /// @param key Details of the incentive to end - /// @return refund The remaining reward tokens when the incentive is ended - function endIncentive(IncentiveKey memory key) external returns (uint256 refund); - - /// @notice Transfers ownership of a deposit from the sender to the given recipient - /// @param tokenId The ID of the token (and the deposit) to transfer - /// @param to The new owner of the deposit - function transferDeposit(uint256 tokenId, address to) external; - - /// @notice Withdraws a Uniswap V3 LP token `tokenId` from this contract to the recipient `to` - /// @param tokenId The unique identifier of an Uniswap V3 LP token - /// @param to The address where the LP token will be sent - /// @param data An optional data array that will be passed along to the `to` address via the NFT safeTransferFrom - function withdrawToken( - uint256 tokenId, - address to, - bytes memory data - ) external; - - /// @notice Stakes a Uniswap V3 LP token - /// @param key The key of the incentive for which to stake the NFT - /// @param tokenId The ID of the token to stake - function stakeToken(IncentiveKey memory key, uint256 tokenId) external; - - /// @notice Unstakes a Uniswap V3 LP token - /// @param key The key of the incentive for which to unstake the NFT - /// @param tokenId The ID of the token to unstake - function unstakeToken(IncentiveKey memory key, uint256 tokenId) external; - - /// @notice Transfers `amountRequested` of accrued `rewardToken` rewards from the contract to the recipient `to` - /// @param rewardToken The token being distributed as a reward - /// @param to The address where claimed rewards will be sent to - /// @param amountRequested The amount of reward tokens to claim. Claims entire reward amount if set to 0. - /// @return reward The amount of reward tokens claimed - function claimReward( - IERC20Minimal rewardToken, - address to, - uint256 amountRequested - ) external returns (uint256 reward); - - /// @notice Calculates the reward amount that will be received for the given stake - /// @param key The key of the incentive - /// @param tokenId The ID of the token - /// @return reward The reward accrued to the NFT for the given incentive thus far - function getRewardInfo(IncentiveKey memory key, uint256 tokenId) - external - returns (uint256 reward, uint160 secondsInsideX128); - - /// @notice Event emitted when a liquidity mining incentive has been created - /// @param rewardToken The token being distributed as a reward - /// @param pool The Uniswap V3 pool - /// @param startTime The time when the incentive program begins - /// @param endTime The time when rewards stop accruing - /// @param refundee The address which receives any remaining reward tokens after the end time - /// @param reward The amount of reward tokens to be distributed - event IncentiveCreated( - IERC20Minimal indexed rewardToken, - IUniswapV3Pool indexed pool, - uint256 startTime, - uint256 endTime, - address refundee, - uint256 reward - ); - - /// @notice Event that can be emitted when a liquidity mining incentive has ended - /// @param incentiveId The incentive which is ending - /// @param refund The amount of reward tokens refunded - event IncentiveEnded(bytes32 indexed incentiveId, uint256 refund); - - /// @notice Emitted when ownership of a deposit changes - /// @param tokenId The ID of the deposit (and token) that is being transferred - /// @param oldOwner The owner before the deposit was transferred - /// @param newOwner The owner after the deposit was transferred - event DepositTransferred(uint256 indexed tokenId, address indexed oldOwner, address indexed newOwner); - - /// @notice Event emitted when a Uniswap V3 LP token has been staked - /// @param tokenId The unique identifier of an Uniswap V3 LP token - /// @param liquidity The amount of liquidity staked - /// @param incentiveId The incentive in which the token is staking - event TokenStaked(uint256 indexed tokenId, bytes32 indexed incentiveId, uint128 liquidity); - - /// @notice Event emitted when a Uniswap V3 LP token has been unstaked - /// @param tokenId The unique identifier of an Uniswap V3 LP token - /// @param incentiveId The incentive in which the token is staking - event TokenUnstaked(uint256 indexed tokenId, bytes32 indexed incentiveId); - - /// @notice Event emitted when a reward token has been claimed - /// @param to The address where claimed rewards were sent to - /// @param reward The amount of reward tokens claimed - event RewardClaimed(address indexed to, uint256 reward); -} diff --git a/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolActions.sol b/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolActions.sol deleted file mode 100644 index 6783a4f9e..000000000 --- a/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolActions.sol +++ /dev/null @@ -1,77 +0,0 @@ -// 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 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); -} diff --git a/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolDerivedState.sol b/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolDerivedState.sol deleted file mode 100644 index a24c28eac..000000000 --- a/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolDerivedState.sol +++ /dev/null @@ -1,22 +0,0 @@ -// 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); -} diff --git a/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolImmutables.sol b/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolImmutables.sol deleted file mode 100644 index 965428235..000000000 --- a/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolImmutables.sol +++ /dev/null @@ -1,21 +0,0 @@ -// 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 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 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); -} diff --git a/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolState.sol b/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolState.sol deleted file mode 100644 index b12fe3ca3..000000000 --- a/src/arbitrum/interfaces/univ3/pool/IUniswapV3PoolState.sol +++ /dev/null @@ -1,51 +0,0 @@ -// 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 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 - ); -} diff --git a/src/arbitrum/lib/univ3/FixedPoint96.sol b/src/arbitrum/lib/univ3/FixedPoint96.sol deleted file mode 100644 index cada0bcd1..000000000 --- a/src/arbitrum/lib/univ3/FixedPoint96.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.4.0; - -/// @title FixedPoint96 -/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) -/// @dev Used in SqrtPriceMath.sol -library FixedPoint96 { - uint8 internal constant RESOLUTION = 96; - uint256 internal constant Q96 = 0x1000000000000000000000000; -} \ No newline at end of file diff --git a/src/arbitrum/lib/univ3/FullMath.sol b/src/arbitrum/lib/univ3/FullMath.sol deleted file mode 100644 index 9985059e2..000000000 --- a/src/arbitrum/lib/univ3/FullMath.sol +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.4.0 <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 { - /// @notice Calculates floor(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 - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - function mulDiv( - uint256 a, - uint256 b, - uint256 denominator - ) internal pure returns (uint256 result) { - // 512-bit multiply [prod1 prod0] = a * b - // Compute the product mod 2**256 and mod 2**256 - 1 - // then 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 - uint256 prod0; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(a, b, not(0)) - prod0 := mul(a, b) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - // Handle non-overflow cases, 256 by 256 division - if (prod1 == 0) { - require(denominator > 0); - assembly { - result := div(prod0, denominator) - } - return result; - } - - // Make sure the result is less than 2**256. - // Also prevents denominator == 0 - require(denominator > prod1); - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0] - // Compute remainder using mulmod - uint256 remainder; - assembly { - remainder := mulmod(a, b, denominator) - } - // Subtract 256 bit number from 512 bit number - assembly { - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator - // Compute largest power of two divisor of denominator. - // Always >= 1. - uint256 twos = -denominator & denominator; - // Divide denominator by power of two - assembly { - denominator := div(denominator, twos) - } - - // Divide [prod1 prod0] by the factors of two - assembly { - prod0 := div(prod0, twos) - } - // Shift in bits from prod1 into prod0. For this we need - // to flip `twos` such that it is 2**256 / twos. - // If twos is zero, then it becomes one - assembly { - twos := add(div(sub(0, twos), twos), 1) - } - 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 - // correct for four bits. That is, denominator * inv = 1 mod 2**4 - uint256 inv = (3 * denominator) ^ 2; - // Now use 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. - inv *= 2 - denominator * inv; // inverse mod 2**8 - inv *= 2 - denominator * inv; // inverse mod 2**16 - inv *= 2 - denominator * inv; // inverse mod 2**32 - inv *= 2 - denominator * inv; // inverse mod 2**64 - inv *= 2 - denominator * inv; // inverse mod 2**128 - inv *= 2 - denominator * inv; // 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 precoditions 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 * inv; - 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( - uint256 a, - uint256 b, - uint256 denominator - ) internal pure returns (uint256 result) { - result = mulDiv(a, b, denominator); - if (mulmod(a, b, denominator) > 0) { - require(result < type(uint256).max); - result++; - } - } -} diff --git a/src/arbitrum/lib/univ3/LiquidityAmounts.sol b/src/arbitrum/lib/univ3/LiquidityAmounts.sol deleted file mode 100644 index b2ca695fc..000000000 --- a/src/arbitrum/lib/univ3/LiquidityAmounts.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -import "./FullMath.sol"; -import "./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/arbitrum/lib/univ3/LowGasSafeMath.sol b/src/arbitrum/lib/univ3/LowGasSafeMath.sol deleted file mode 100644 index 977bcfdf8..000000000 --- a/src/arbitrum/lib/univ3/LowGasSafeMath.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.6.12; - -/// @title Optimized overflow and underflow safe math operations -/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost -library LowGasSafeMath { - /// @notice Returns x + y, reverts if sum overflows uint256 - /// @param x The augend - /// @param y The addend - /// @return z The sum of x and y - function add(uint256 x, uint256 y) internal pure returns (uint256 z) { - require((z = x + y) >= x); - } - - /// @notice Returns x - y, reverts if underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { - require((z = x - y) <= x); - } - - /// @notice Returns x * y, reverts if overflows - /// @param x The multiplicand - /// @param y The multiplier - /// @return z The product of x and y - function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { - require(x == 0 || (z = x * y) / x == y); - } - - /// @notice Returns x - y, reverts if underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub( - uint256 x, - uint256 y, - string memory errorMessage - ) internal pure returns (uint256 z) { - require((z = x - y) <= x, errorMessage); - } - - /// @notice Returns x + y, reverts if overflows or underflows - /// @param x The augend - /// @param y The addend - /// @return z The sum of x and y - function add(int256 x, int256 y) internal pure returns (int256 z) { - require((z = x + y) >= x == (y >= 0)); - } - - /// @notice Returns x - y, reverts if overflows or underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub(int256 x, int256 y) internal pure returns (int256 z) { - require((z = x - y) <= x == (y >= 0)); - } - - /// @notice Returns x + y, reverts if sum overflows uint128 - /// @param x The augend - /// @param y The addend - /// @return z The sum of x and y - function add128(uint128 x, uint128 y) internal pure returns (uint128 z) { - require((z = x + y) >= x); - } - - /// @notice Returns x - y, reverts if underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub128(uint128 x, uint128 y) internal pure returns (uint128 z) { - require((z = x - y) <= x); - } - - /// @notice Returns x * y, reverts if overflows - /// @param x The multiplicand - /// @param y The multiplier - /// @return z The product of x and y - function mul128(uint128 x, uint128 y) internal pure returns (uint128 z) { - require(x == 0 || (z = x * y) / x == y); - } - - /// @notice Returns x + y, reverts if sum overflows uint128 - /// @param x The augend - /// @param y The addend - /// @return z The sum of x and y - function add160(uint160 x, uint160 y) internal pure returns (uint160 z) { - require((z = x + y) >= x); - } - - /// @notice Returns x - y, reverts if underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub160(uint160 x, uint160 y) internal pure returns (uint160 z) { - require((z = x - y) <= x); - } - - /// @notice Returns x * y, reverts if overflows - /// @param x The multiplicand - /// @param y The multiplier - /// @return z The product of x and y - function mul160(uint160 x, uint160 y) internal pure returns (uint160 z) { - require(x == 0 || (z = x * y) / x == y); - } -} diff --git a/src/arbitrum/lib/univ3/PoolActions.sol b/src/arbitrum/lib/univ3/PoolActions.sol deleted file mode 100644 index 43b0f7ae3..000000000 --- a/src/arbitrum/lib/univ3/PoolActions.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity >=0.6.12; -pragma experimental ABIEncoderV2; - -import "../../interfaces/univ3/IUniswapV3Pool.sol"; -import "./PoolVariables.sol"; -import "./SafeCast.sol"; - -/// @title This library is created to conduct a variety of burn liquidity methods -library PoolActions { - using PoolVariables for IUniswapV3Pool; - using LowGasSafeMath for uint256; - using SafeCast for uint256; - - /** - * @notice Withdraws liquidity in share proportion to the Sorbetto's totalSupply. - * @param pool Uniswap V3 pool - * @param tickLower The lower tick of the range - * @param tickUpper The upper tick of the range - * @param totalSupply The amount of total shares in existence - * @param share to burn - * @param to Recipient of amounts - * @return amount0 Amount of token0 withdrawed - * @return amount1 Amount of token1 withdrawed - */ - function burnLiquidityShare( - IUniswapV3Pool pool, - int24 tickLower, - int24 tickUpper, - uint256 totalSupply, - uint256 share, - address to - ) internal returns (uint256 amount0, uint256 amount1) { - require(totalSupply > 0, "TS"); - uint128 liquidityInPool = pool.positionLiquidity(tickLower, tickUpper); - uint256 liquidity = uint256(liquidityInPool).mul(share) / totalSupply; - - if (liquidity > 0) { - (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity.toUint128()); - - if (amount0 > 0 || amount1 > 0) { - // collect liquidity share - (amount0, amount1) = pool.collect(to, tickLower, tickUpper, amount0.toUint128(), amount1.toUint128()); - } - } - } - - /** - * @notice Withdraws exact amount of liquidity - * @param pool Uniswap V3 pool - * @param tickLower The lower tick of the range - * @param tickUpper The upper tick of the range - * @param liquidity to burn - * @param to Recipient of amounts - * @return amount0 Amount of token0 withdrawed - * @return amount1 Amount of token1 withdrawed - */ - function burnExactLiquidity( - IUniswapV3Pool pool, - int24 tickLower, - int24 tickUpper, - uint128 liquidity, - address to - ) internal returns (uint256 amount0, uint256 amount1) { - uint128 liquidityInPool = pool.positionLiquidity(tickLower, tickUpper); - require(liquidityInPool >= liquidity, "TML"); - (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity); - - if (amount0 > 0 || amount1 > 0) { - // collect liquidity share including earned fees - (amount0, amount0) = pool.collect(to, tickLower, tickUpper, amount0.toUint128(), amount1.toUint128()); - } - } - - /** - * @notice Withdraws all liquidity in a range from Uniswap pool - * @param pool Uniswap V3 pool - * @param tickLower The lower tick of the range - * @param tickUpper The upper tick of the range - */ - function burnAllLiquidity( - IUniswapV3Pool pool, - int24 tickLower, - int24 tickUpper - ) internal { - // Burn all liquidity in this range - uint128 liquidity = pool.positionLiquidity(tickLower, tickUpper); - if (liquidity > 0) { - pool.burn(tickLower, tickUpper, liquidity); - } - - // Collect all owed tokens - pool.collect(address(this), tickLower, tickUpper, type(uint128).max, type(uint128).max); - } -} diff --git a/src/arbitrum/lib/univ3/PoolVariables.sol b/src/arbitrum/lib/univ3/PoolVariables.sol deleted file mode 100644 index 8b64ec2fa..000000000 --- a/src/arbitrum/lib/univ3/PoolVariables.sol +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0 <0.8.0; - -import "./LiquidityAmounts.sol"; -import "../../interfaces/univ3/IUniswapV3Pool.sol"; -import "./TickMath.sol"; -import "./PositionKey.sol"; -import "./LowGasSafeMath.sol"; -import "./SqrtPriceMath.sol"; - -/// @title Liquidity and ticks functions -/// @notice Provides functions for computing liquidity and ticks for token amounts and prices -library PoolVariables { - using LowGasSafeMath for uint256; - - // Cache struct for calculations - struct Info { - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0; - uint256 amount1; - uint128 liquidity; - int24 tickLower; - int24 tickUpper; - } - - /// @dev Wrapper around `LiquidityAmounts.getAmountsForLiquidity()`. - /// @param pool Uniswap V3 pool - /// @param liquidity The liquidity being valued - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return amounts of token0 and token1 that corresponds to liquidity - function amountsForLiquidity( - IUniswapV3Pool pool, - uint128 liquidity, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint256, uint256) { - //Get current price from the pool - (uint160 sqrtRatioX96, , , , , , ) = pool.slot0(); - return - LiquidityAmounts.getAmountsForLiquidity( - sqrtRatioX96, - TickMath.getSqrtRatioAtTick(_tickLower), - TickMath.getSqrtRatioAtTick(_tickUpper), - liquidity - ); - } - - /// @dev Wrapper around `LiquidityAmounts.getLiquidityForAmounts()`. - /// @param pool Uniswap V3 pool - /// @param amount0 The amount of token0 - /// @param amount1 The amount of token1 - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return The maximum amount of liquidity that can be held amount0 and amount1 - function liquidityForAmounts( - IUniswapV3Pool pool, - uint256 amount0, - uint256 amount1, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint128) { - //Get current price from the pool - (uint160 sqrtRatioX96, , , , , , ) = pool.slot0(); - - return - LiquidityAmounts.getLiquidityForAmounts( - sqrtRatioX96, - TickMath.getSqrtRatioAtTick(_tickLower), - TickMath.getSqrtRatioAtTick(_tickUpper), - amount0, - amount1 - ); - } - - /// @dev Amounts of token0 and token1 held in contract position. - /// @param pool Uniswap V3 pool - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return amount0 The amount of token0 held in position - /// @return amount1 The amount of token1 held in position - function positionAmounts( - IUniswapV3Pool pool, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint256 amount0, uint256 amount1) { - //Compute position key - bytes32 positionKey = PositionKey.compute(address(this), _tickLower, _tickUpper); - //Get Position.Info for specified ticks - (uint128 liquidity, , , uint128 tokensOwed0, uint128 tokensOwed1) = pool.positions(positionKey); - // Calc amounts of token0 and token1 including fees - (amount0, amount1) = amountsForLiquidity(pool, liquidity, _tickLower, _tickUpper); - amount0 = amount0.add(uint256(tokensOwed0)); - amount1 = amount1.add(uint256(tokensOwed1)); - } - - /// @dev Amount of liquidity in contract position. - /// @param pool Uniswap V3 pool - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return liquidity stored in position - function positionLiquidity( - IUniswapV3Pool pool, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint128 liquidity) { - //Compute position key - bytes32 positionKey = PositionKey.compute(address(this), _tickLower, _tickUpper); - //Get liquidity stored in position - (liquidity, , , , ) = pool.positions(positionKey); - } - - /// @dev Common checks for valid tick inputs. - /// @param tickLower The lower tick of the range - /// @param tickUpper The upper tick of the range - function checkRange(int24 tickLower, int24 tickUpper) internal pure { - require(tickLower < tickUpper, "TLU"); - require(tickLower >= TickMath.MIN_TICK, "TLM"); - require(tickUpper <= TickMath.MAX_TICK, "TUM"); - } - - /// @dev Rounds tick down towards negative infinity so that it's a multiple - /// of `tickSpacing`. - function floor(int24 tick, int24 tickSpacing) internal pure returns (int24) { - int24 compressed = tick / tickSpacing; - if (tick < 0 && tick % tickSpacing != 0) compressed--; - return compressed * tickSpacing; - } - - /// @dev Gets ticks with proportion equivalent to desired amount - /// @param pool Uniswap V3 pool - /// @param amount0Desired The desired amount of token0 - /// @param amount1Desired The desired amount of token1 - /// @param baseThreshold The range for upper and lower ticks - /// @param tickSpacing The pool tick spacing - /// @return tickLower The lower tick of the range - /// @return tickUpper The upper tick of the range - function getPositionTicks( - IUniswapV3Pool pool, - uint256 amount0Desired, - uint256 amount1Desired, - int24 baseThreshold, - int24 tickSpacing - ) internal view returns (int24 tickLower, int24 tickUpper) { - Info memory cache = Info(amount0Desired, amount1Desired, 0, 0, 0, 0, 0); - // Get current price and tick from the pool - (uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0(); - //Calc base ticks - (cache.tickLower, cache.tickUpper) = baseTicks(currentTick, baseThreshold, tickSpacing); - //Calc amounts of token0 and token1 that can be stored in base range - (cache.amount0, cache.amount1) = amountsForTicks( - pool, - cache.amount0Desired, - cache.amount1Desired, - cache.tickLower, - cache.tickUpper - ); - //Liquidity that can be stored in base range - cache.liquidity = liquidityForAmounts(pool, cache.amount0, cache.amount1, cache.tickLower, cache.tickUpper); - //Get imbalanced token - bool zeroGreaterOne = amountsDirection( - cache.amount0Desired, - cache.amount1Desired, - cache.amount0, - cache.amount1 - ); - //Calc new tick(upper or lower) for imbalanced token - if (zeroGreaterOne) { - uint160 nextSqrtPrice0 = SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp( - sqrtPriceX96, - cache.liquidity, - cache.amount0Desired, - false - ); - cache.tickUpper = PoolVariables.floor(TickMath.getTickAtSqrtRatio(nextSqrtPrice0), tickSpacing); - } else { - uint160 nextSqrtPrice1 = SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown( - sqrtPriceX96, - cache.liquidity, - cache.amount1Desired, - false - ); - cache.tickLower = PoolVariables.floor(TickMath.getTickAtSqrtRatio(nextSqrtPrice1), tickSpacing); - } - checkRange(cache.tickLower, cache.tickUpper); - - tickLower = cache.tickLower; - tickUpper = cache.tickUpper; - } - - /// @dev Gets amounts of token0 and token1 that can be stored in range of upper and lower ticks - /// @param pool Uniswap V3 pool - /// @param amount0Desired The desired amount of token0 - /// @param amount1Desired The desired amount of token1 - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return amount0 amounts of token0 that can be stored in range - /// @return amount1 amounts of token1 that can be stored in range - function amountsForTicks( - IUniswapV3Pool pool, - uint256 amount0Desired, - uint256 amount1Desired, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint256 amount0, uint256 amount1) { - uint128 liquidity = liquidityForAmounts(pool, amount0Desired, amount1Desired, _tickLower, _tickUpper); - - (amount0, amount1) = amountsForLiquidity(pool, liquidity, _tickLower, _tickUpper); - } - - /// @dev Calc base ticks depending on base threshold and tickspacing - function baseTicks( - int24 currentTick, - int24 baseThreshold, - int24 tickSpacing - ) internal pure returns (int24 tickLower, int24 tickUpper) { - int24 tickFloor = floor(currentTick, tickSpacing); - - tickLower = tickFloor - baseThreshold; - tickUpper = tickFloor + baseThreshold; - } - - /// @dev Get imbalanced token - /// @param amount0Desired The desired amount of token0 - /// @param amount1Desired The desired amount of token1 - /// @param amount0 Amounts of token0 that can be stored in base range - /// @param amount1 Amounts of token1 that can be stored in base range - /// @return zeroGreaterOne true if token0 is imbalanced. False if token1 is imbalanced - function amountsDirection( - uint256 amount0Desired, - uint256 amount1Desired, - uint256 amount0, - uint256 amount1 - ) internal pure returns (bool zeroGreaterOne) { - zeroGreaterOne = amount0Desired.sub(amount0).mul(amount1Desired) > - amount1Desired.sub(amount1).mul(amount0Desired) - ? true - : false; - } - - // Check price has not moved a lot recently. This mitigates price - // manipulation during rebalance and also prevents placing orders - // when it's too volatile. - function checkDeviation( - IUniswapV3Pool pool, - int24 maxTwapDeviation, - uint32 twapDuration - ) internal view { - (, int24 currentTick, , , , , ) = pool.slot0(); - int24 twap = getTwap(pool, twapDuration); - int24 deviation = currentTick > twap ? currentTick - twap : twap - currentTick; - require(deviation <= maxTwapDeviation, "PSC"); - } - - /// @dev Fetches time-weighted average price in ticks from Uniswap pool for specified duration - function getTwap(IUniswapV3Pool pool, uint32 twapDuration) internal view returns (int24) { - uint32 _twapDuration = twapDuration; - uint32[] memory secondsAgo = new uint32[](2); - secondsAgo[0] = _twapDuration; - secondsAgo[1] = 0; - - (int56[] memory tickCumulatives, ) = pool.observe(secondsAgo); - return int24((tickCumulatives[1] - tickCumulatives[0]) / _twapDuration); - } -} diff --git a/src/arbitrum/lib/univ3/PositionKey.sol b/src/arbitrum/lib/univ3/PositionKey.sol deleted file mode 100644 index 6febb735a..000000000 --- a/src/arbitrum/lib/univ3/PositionKey.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -library PositionKey { - /// @dev Returns the key of the position in the core library - function compute( - address owner, - int24 tickLower, - int24 tickUpper - ) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(owner, tickLower, tickUpper)); - } -} \ No newline at end of file diff --git a/src/arbitrum/lib/univ3/SafeCast.sol b/src/arbitrum/lib/univ3/SafeCast.sol deleted file mode 100644 index 53b70b3cf..000000000 --- a/src/arbitrum/lib/univ3/SafeCast.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @title Safe casting methods -/// @notice Contains methods for safely casting between types -library SafeCast { - /// @notice Cast a uint256 to a uint160, revert on overflow - /// @param y The uint256 to be downcasted - /// @return z The downcasted integer, now type uint160 - function toUint160(uint256 y) internal pure returns (uint160 z) { - require((z = uint160(y)) == y); - } - - /// @notice Cast a uint256 to a uint128, revert on overflow - /// @param y The uint256 to be downcasted - /// @return z The downcasted integer, now type uint128 - function toUint128(uint256 y) internal pure returns (uint128 z) { - require((z = uint128(y)) == y); - } - - /// @notice Cast a int256 to a int128, revert on overflow or underflow - /// @param y The int256 to be downcasted - /// @return z The downcasted integer, now type int128 - function toInt128(int256 y) internal pure returns (int128 z) { - require((z = int128(y)) == y); - } - - /// @notice Cast a uint256 to a int256, revert on overflow - /// @param y The uint256 to be casted - /// @return z The casted integer, now type int256 - function toInt256(uint256 y) internal pure returns (int256 z) { - require(y < 2**255); - z = int256(y); - } -} diff --git a/src/arbitrum/lib/univ3/SqrtPriceMath.sol b/src/arbitrum/lib/univ3/SqrtPriceMath.sol deleted file mode 100644 index d73787e29..000000000 --- a/src/arbitrum/lib/univ3/SqrtPriceMath.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "./LowGasSafeMath.sol"; -import "./SafeCast.sol"; - -import "./FullMath.sol"; -import "./UnsafeMath.sol"; -import "./FixedPoint96.sol"; - -/// @title Functions based on Q64.96 sqrt price and liquidity -/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas -library SqrtPriceMath { - using LowGasSafeMath for uint256; - using SafeCast for uint256; - - /// @notice Gets the next sqrt price given a delta of token0 - /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least - /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the - /// price less in order to not send too much output. - /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), - /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). - /// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta - /// @param liquidity The amount of usable liquidity - /// @param amount How much of token0 to add or remove from virtual reserves - /// @param add Whether to add or remove the amount of token0 - /// @return The price after adding or removing amount, depending on add - function getNextSqrtPriceFromAmount0RoundingUp( - uint160 sqrtPX96, - uint128 liquidity, - uint256 amount, - bool add - ) internal pure returns (uint160) { - // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price - if (amount == 0) return sqrtPX96; - uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; - - if (add) { - uint256 product; - if ((product = amount * sqrtPX96) / amount == sqrtPX96) { - uint256 denominator = numerator1 + product; - if (denominator >= numerator1) - // always fits in 160 bits - return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); - } - - return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))); - } else { - uint256 product; - // if the product overflows, we know the denominator underflows - // in addition, we must check that the denominator does not underflow - require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); - uint256 denominator = numerator1 - product; - return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); - } - } - - /// @notice Gets the next sqrt price given a delta of token1 - /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least - /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the - /// price less in order to not send too much output. - /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity - /// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta - /// @param liquidity The amount of usable liquidity - /// @param amount How much of token1 to add, or remove, from virtual reserves - /// @param add Whether to add, or remove, the amount of token1 - /// @return The price after adding or removing `amount` - function getNextSqrtPriceFromAmount1RoundingDown( - uint160 sqrtPX96, - uint128 liquidity, - uint256 amount, - bool add - ) internal pure returns (uint160) { - // if we're adding (subtracting), rounding down requires rounding the quotient down (up) - // in both cases, avoid a mulDiv for most inputs - if (add) { - uint256 quotient = ( - amount <= type(uint160).max - ? (amount << FixedPoint96.RESOLUTION) / liquidity - : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) - ); - - return uint256(sqrtPX96).add(quotient).toUint160(); - } else { - uint256 quotient = ( - amount <= type(uint160).max - ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) - : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) - ); - - require(sqrtPX96 > quotient); - // always fits 160 bits - return uint160(sqrtPX96 - quotient); - } - } -} diff --git a/src/arbitrum/lib/univ3/TickMath.sol b/src/arbitrum/lib/univ3/TickMath.sol deleted file mode 100644 index ee48fee54..000000000 --- a/src/arbitrum/lib/univ3/TickMath.sol +++ /dev/null @@ -1,205 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0 <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) { - uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); - require(absTick <= uint256(MAX_TICK), 'T'); - - uint256 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(uint256).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'); - uint256 ratio = uint256(sqrtPriceX96) << 32; - - uint256 r = ratio; - uint256 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); - - int256 log_2 = (int256(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)) - } - - int256 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/arbitrum/lib/univ3/UnsafeMath.sol b/src/arbitrum/lib/univ3/UnsafeMath.sol deleted file mode 100644 index e5944c9d7..000000000 --- a/src/arbitrum/lib/univ3/UnsafeMath.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @title Math functions that do not check inputs or outputs -/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks -library UnsafeMath { - /// @notice Returns ceil(x / y) - /// @dev division by 0 has unspecified behavior, and must be checked externally - /// @param x The dividend - /// @param y The divisor - /// @return z The quotient, ceil(x / y) - function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { - assembly { - z := add(div(x, y), gt(mod(x, y), 0)) - } - } - - /// @notice Returns floor(x / y) - /// @dev division by 0 has unspecified behavior, and must be checked externally - /// @param x The dividend - /// @param y The divisor - /// @return z The quotient, floor(x / y) - function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 z) { - assembly { - z := div(x, y) - } - } -} diff --git a/src/arbitrum/pickle-jar-univ3.sol b/src/arbitrum/pickle-jar-univ3.sol deleted file mode 100644 index 569e7f4a7..000000000 --- a/src/arbitrum/pickle-jar-univ3.sol +++ /dev/null @@ -1,318 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.12; -pragma experimental ABIEncoderV2; - -// ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── -// ─██████████████─██████████─██████████████─██████──████████─██████─────────██████████████────────────██████─██████████████─████████████████─── -// ─██░░░░░░░░░░██─██░░░░░░██─██░░░░░░░░░░██─██░░██──██░░░░██─██░░██─────────██░░░░░░░░░░██────────────██░░██─██░░░░░░░░░░██─██░░░░░░░░░░░░██─── -// ─██░░██████░░██─████░░████─██░░██████████─██░░██──██░░████─██░░██─────────██░░██████████────────────██░░██─██░░██████░░██─██░░████████░░██─── -// ─██░░██──██░░██───██░░██───██░░██─────────██░░██──██░░██───██░░██─────────██░░██────────────────────██░░██─██░░██──██░░██─██░░██────██░░██─── -// ─██░░██████░░██───██░░██───██░░██─────────██░░██████░░██───██░░██─────────██░░██████████────────────██░░██─██░░██████░░██─██░░████████░░██─── -// ─██░░░░░░░░░░██───██░░██───██░░██─────────██░░░░░░░░░░██───██░░██─────────██░░░░░░░░░░██────────────██░░██─██░░░░░░░░░░██─██░░░░░░░░░░░░██─── -// ─██░░██████████───██░░██───██░░██─────────██░░██████░░██───██░░██─────────██░░██████████────██████──██░░██─██░░██████░░██─██░░██████░░████─── -// ─██░░██───────────██░░██───██░░██─────────██░░██──██░░██───██░░██─────────██░░██────────────██░░██──██░░██─██░░██──██░░██─██░░██──██░░██───── -// ─██░░██─────────████░░████─██░░██████████─██░░██──██░░████─██░░██████████─██░░██████████────██░░██████░░██─██░░██──██░░██─██░░██──██░░██████─ -// ─██░░██─────────██░░░░░░██─██░░░░░░░░░░██─██░░██──██░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██────██░░░░░░░░░░██─██░░██──██░░██─██░░██──██░░░░░░██─ -// ─██████─────────██████████─██████████████─██████──████████─██████████████─██████████████────██████████████─██████──██████─██████──██████████─ -// ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - -import "../interfaces/controllerv2.sol"; -import "../lib/erc20.sol"; -import "../lib/univ3/PoolActions.sol"; -import "../lib/reentrancy-guard.sol"; -import "../lib/safe-math.sol"; -import "../interfaces/univ3/IUniswapV3PositionsNFT.sol"; -import "../interfaces/univ3/IUniswapV3Pool.sol"; -import "./interfaces/univ3/ISwapRouter.sol"; -import "../interfaces/weth.sol"; - -contract PickleJarUniV3Arbitrum is ERC20, ReentrancyGuard { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - using SafeMath for uint128; - using PoolVariables for IUniswapV3Pool; - - address public constant weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; - address public constant univ3Router = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; - - address public governance; - address public timelock; - address public controller; - - bool public paused; - - IUniswapV3Pool public pool; - - IERC20 public token0; - IERC20 public token1; - - constructor( - string memory _name, - string memory _symbol, - address _pool, - address _governance, - address _timelock, - address _controller - ) public ERC20(_name, _symbol) { - pool = IUniswapV3Pool(_pool); - token0 = IERC20(pool.token0()); - token1 = IERC20(pool.token1()); - - governance = _governance; - timelock = _timelock; - controller = _controller; - paused = false; - } - - function totalLiquidity() public view returns (uint256) { - return liquidityOfThis().add(IControllerV2(controller).liquidityOf(address(pool))); - } - - function liquidityOfThis() public view returns (uint256) { - uint256 _balance0 = token0.balanceOf(address(this)); - uint256 _balance1 = token1.balanceOf(address(this)); - return uint256(pool.liquidityForAmounts(_balance0, _balance1, getLowerTick(), getUpperTick())); - } - - function getUpperTick() public view returns (int24) { - return IControllerV2(controller).getUpperTick(address(pool)); - } - - function getLowerTick() public view returns (int24) { - return IControllerV2(controller).getLowerTick(address(pool)); - } - - function setGovernance(address _governance) public { - require(msg.sender == governance, "!governance"); - governance = _governance; - } - - function setTimelock(address _timelock) public { - require(msg.sender == timelock, "!timelock"); - timelock = _timelock; - } - - function setController(address _controller) public { - require(msg.sender == timelock, "!timelock"); - controller = _controller; - } - - event JarPaused(uint256 block, uint256 timestamp); - - function setPaused(bool _paused) external { - require(msg.sender == governance, "!governance"); - paused = _paused; - emit JarPaused(block.number, block.timestamp); - } - - function earn() public { - require(liquidityOfThis() > 0, "no liquidity here"); - - uint256 balance0 = token0.balanceOf(address(this)); - uint256 balance1 = token1.balanceOf(address(this)); - - token0.safeTransfer(controller, balance0); - token1.safeTransfer(controller, balance1); - - IControllerV2(controller).earn(address(pool), balance0, balance1); - } - - function deposit(uint256 token0Amount, uint256 token1Amount) external payable nonReentrant whenNotPaused { - bool _ethUsed; - (token0Amount, token1Amount, _ethUsed) = _convertEth(token0Amount, token1Amount); - - _deposit(token0Amount, token1Amount); - - uint256 _liquidity = _refundUnused(_ethUsed); - - uint256 shares = 0; - if (totalSupply() == 0) { - shares = _liquidity; - } else { - shares = (_liquidity.mul(totalSupply())).div(IControllerV2(controller).liquidityOf(address(pool))); - } - - _mint(msg.sender, shares); - - earn(); - } - - function getProportion() public view returns (uint256) { - (uint256 a1, uint256 a2) = pool.amountsForLiquidity(1e18, getLowerTick(), getUpperTick()); - return (a2 * (10**18)) / a1; - } - - function withdrawAll() external { - withdraw(balanceOf(msg.sender)); - } - - function withdraw(uint256 _shares) public nonReentrant whenNotPaused { - uint256 r = (totalLiquidity().mul(_shares)).div(totalSupply()); - (uint256 _expectA0, uint256 _expectA1) = pool.amountsForLiquidity(uint128(r), getLowerTick(), getUpperTick()); - _burn(msg.sender, _shares); - // Check balance - uint256[2] memory _balances = [token0.balanceOf(address(this)), token1.balanceOf(address(this))]; - uint256 b = liquidityOfThis(); - - if (b < r) { - uint256 _withdraw = r.sub(b); - (uint256 _a0, uint256 _a1) = IControllerV2(controller).withdraw(address(pool), _withdraw); - _expectA0 = _balances[0].add(_a0); - _expectA1 = _balances[1].add(_a1); - } - - token0.safeTransfer(msg.sender, _expectA0); - token1.safeTransfer(msg.sender, _expectA1); - } - - function getRatio() public view returns (uint256) { - if (totalSupply() == 0) return 0; - return totalLiquidity().mul(1e18).div(totalSupply()); - } - - function _deposit(uint256 token0Amount, uint256 token1Amount) internal { - if (!(token0.balanceOf(address(this)) >= token0Amount) && (token0Amount != 0)) - token0.safeTransferFrom(msg.sender, address(this), token0Amount); - - if (!(token1.balanceOf(address(this)) >= token1Amount) && (token1Amount != 0)) - token1.safeTransferFrom(msg.sender, address(this), token1Amount); - - _balanceProportion(getLowerTick(), getUpperTick()); - } - - function _convertEth(uint256 token0Amount, uint256 token1Amount) - internal - returns ( - uint256, - uint256, - bool - ) - { - bool _ethUsed = false; - uint256 _eth = msg.value; - if (_eth > 0) { - WETH(weth).deposit{value: _eth}(); - - if (address(token0) == weth) { - token0Amount = _eth; - _ethUsed = true; - } else if (address(token1) == weth) { - token1Amount = _eth; - _ethUsed = true; - } - } - return (token0Amount, token1Amount, _ethUsed); - } - - function _refundEth(uint256 _refund) internal { - WETH(weth).withdraw(_refund); - (bool sent, bytes memory data) = (msg.sender).call{value: _refund}(""); - require(sent, "Failed to refund Eth"); - } - - function _refundUnused(bool _ethUsed) internal returns (uint256) { - PoolVariables.Info memory _cache; - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(getLowerTick()); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(getUpperTick()); - - _cache.liquidity = uint128( - LiquidityAmounts.getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, _cache.amount0Desired).add( - LiquidityAmounts.getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, _cache.amount1Desired) - ) - ); - - (_cache.amount0, _cache.amount1) = LiquidityAmounts.getAmountsForLiquidity( - sqrtPriceX96, - sqrtRatioAX96, - sqrtRatioBX96, - _cache.liquidity - ); - - if (_cache.amount0Desired > _cache.amount0) - if ((address(token0) == address(weth)) && _ethUsed) _refundEth(_cache.amount0Desired.sub(_cache.amount0)); - else { - token0.safeTransfer(msg.sender, _cache.amount0Desired.sub(_cache.amount0)); - } - - if (_cache.amount1Desired > _cache.amount1) - if ((address(token1) == address(weth)) && _ethUsed) _refundEth(_cache.amount1Desired.sub(_cache.amount1)); - else { - token1.safeTransfer(msg.sender, _cache.amount1Desired.sub(_cache.amount1)); - } - return _cache.liquidity; - } - - function _balanceProportion(int24 _tickLower, int24 _tickUpper) internal { - PoolVariables.Info memory _cache; - - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(_tickLower); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(_tickUpper); - - _cache.liquidity = uint128( - LiquidityAmounts.getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, _cache.amount0Desired).add( - LiquidityAmounts.getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, _cache.amount1Desired) - ) - ); - - (_cache.amount0, _cache.amount1) = LiquidityAmounts.getAmountsForLiquidity( - sqrtPriceX96, - sqrtRatioAX96, - sqrtRatioBX96, - _cache.liquidity - ); - - //Determine Trade Direction - bool _zeroForOne = _cache.amount0Desired > _cache.amount0 ? true : false; - - //Determine Amount to swap - uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0)) - : (_cache.amount1Desired.sub(_cache.amount1)); - - if (_amountSpecified > 0) { - //Determine Token to swap - address _inputToken = _zeroForOne ? address(token0) : address(token1); - - IERC20(_inputToken).safeApprove(univ3Router, 0); - IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); - - //Swap the token imbalanced - ISwapRouter(univ3Router).exactInputSingle( - ISwapRouter.ExactInputSingleParams({ - tokenIn: _inputToken, - tokenOut: _zeroForOne ? address(token1) : address(token0), - fee: pool.fee(), - recipient: address(this), - amountIn: _amountSpecified, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }) - ); - } - } - - modifier whenNotPaused() { - require(paused == false, "paused"); - _; - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC721Received.selector; - } - - fallback() external payable {} -} diff --git a/src/interfaces/IRewarder.sol b/src/interfaces/IRewarder.sol index e458a0604..9355de0b5 100644 --- a/src/interfaces/IRewarder.sol +++ b/src/interfaces/IRewarder.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT +pragma solidity >=0.6.7; + +import {IERC20} from "../lib/erc20.sol"; -pragma solidity ^0.6.7; -import "../lib/BoringERC20.sol"; interface IRewarder { - using BoringERC20 for IERC20; function onSushiReward(uint256 pid, address user, address recipient, uint256 sushiAmount, uint256 newLpAmount) external; function pendingTokens(uint256 pid, address user, uint256 sushiAmount) external view returns (IERC20[] memory, uint256[] memory); } \ No newline at end of file diff --git a/src/interfaces/master-bento.sol b/src/interfaces/bentobox.sol similarity index 92% rename from src/interfaces/master-bento.sol rename to src/interfaces/bentobox.sol index d01542b31..238e60df4 100644 --- a/src/interfaces/master-bento.sol +++ b/src/interfaces/bentobox.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; /// @notice Trident pool router interface. -interface IMasterBento { +interface IBentoBox { function setMasterContractApproval( address user, address masterContract, diff --git a/src/interfaces/controllerv2.sol b/src/interfaces/controllerv2.sol index ed52aadfc..04fccd516 100644 --- a/src/interfaces/controllerv2.sol +++ b/src/interfaces/controllerv2.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT - -pragma solidity ^0.6.12; +pragma solidity >=0.6.12; interface IControllerV2 { function jars(address) external view returns (address); diff --git a/src/interfaces/fraxswap-router.sol b/src/interfaces/fraxswap-router.sol new file mode 100644 index 000000000..669f210c8 --- /dev/null +++ b/src/interfaces/fraxswap-router.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity >=0.8.0; + +interface IFraxswapRouterMultihop { + /// @notice Input to the swap function + /// @dev contains the top level info for the swap + /// @param tokenIn input token address + /// @param amountIn input token amount + /// @param tokenOut output token address + /// @param amountOutMinimum output token minimum amount (reverts if lower) + /// @param recipient recipient of the output token + /// @param deadline deadline for this swap transaction (reverts if exceeded) + /// @param v v value for permit signature if supported + /// @param r r value for permit signature if supported + /// @param s s value for permit signature if supported + /// @param route byte encoded + struct FraxswapParams { + address tokenIn; + uint256 amountIn; + address tokenOut; + uint256 amountOutMinimum; + address recipient; + uint256 deadline; + bool approveMax; + uint8 v; + bytes32 r; + bytes32 s; + bytes route; + } + + /// @notice A hop along the swap route + /// @dev a series of swaps (steps) containing the same input token and output token + /// @param tokenOut output token address + /// @param amountOut output token amount + /// @param percentOfHop amount of output tokens from the previous hop going into this hop (10000 = 100%) + /// @param steps list of swaps from the same input token to the same output token + /// @param nextHops next hops from this one, the next hops' input token is the tokenOut + struct FraxswapRoute { + address tokenOut; + uint256 amountOut; + uint256 percentOfHop; + bytes[] steps; + bytes[] nextHops; + } + + /// @notice A swap step in a specific route. routes can have multiple swap steps + /// @dev a single swap to a token from the token of the previous hop in the route + /// @param swapType type of the swap performed (Uniswap V2, Fraxswap,Curve, etc) + /// @param directFundNextPool 0 = funds go via router, 1 = fund are sent directly to pool + /// @param directFundThisPool 0 = funds go via router, 1 = fund are sent directly to pool + /// @param tokenOut the target token of the step. output token address + /// @param pool address of the pool to use in the step + /// @param extraParam1 extra data used in the step + /// @param extraParam2 extra data used in the step + /// @param percentOfHop percentage of all of the steps in this route (hop) + struct FraxswapStepData { + uint8 swapType; + uint8 directFundNextPool; + uint8 directFundThisPool; + address tokenOut; + address pool; + uint256 extraParam1; + uint256 extraParam2; + uint256 percentOfHop; + } + + function swap(FraxswapParams memory params) external payable returns (uint256 amountOut); +} diff --git a/src/interfaces/masterchef-rewarder.sol b/src/interfaces/masterchef-rewarder.sol index 8081fdb3d..e3363f101 100644 --- a/src/interfaces/masterchef-rewarder.sol +++ b/src/interfaces/masterchef-rewarder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; +pragma solidity >=0.6.7; import "../lib/erc20.sol"; // interface for Sushiswap MasterChef contract interface IMasterchefRewarder { diff --git a/src/interfaces/minichefv2.sol b/src/interfaces/minichefv2.sol index 765a93e95..cf7ede825 100644 --- a/src/interfaces/minichefv2.sol +++ b/src/interfaces/minichefv2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; +pragma solidity >=0.6.7; pragma experimental ABIEncoderV2; import "./IRewarder.sol"; diff --git a/src/interfaces/strategy-univ3.sol b/src/interfaces/strategy-univ3.sol new file mode 100644 index 000000000..2ff0cce34 --- /dev/null +++ b/src/interfaces/strategy-univ3.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.12; + +interface IStrategyUniV3 { + function tick_lower() external view returns (int24); + + function tick_upper() external view returns (int24); + + function balanceProportion(int24, int24) external; + + function pool() external view returns (address); + + function timelock() external view returns (address); + + function deposit() external; + + function balanceAndDeposit(uint256 amount0Desired, uint256 amount1Desired) + external + returns ( + uint128 liquidity, + uint256 unusedAmount0, + uint256 unusedAmount1 + ); + + function withdraw(address) external; + + function withdraw(uint256) external returns (uint256, uint256); + + function withdrawAll() external returns (uint256, uint256); + + function liquidityOf() external view returns (uint256); + + function harvest() external; + + function rebalance() external; + + function setTimelock(address) external; + + function setController(address _controller) external; +} diff --git a/src/interfaces/trident-router.sol b/src/interfaces/trident-router.sol index 6ae23a205..972ede23d 100644 --- a/src/interfaces/trident-router.sol +++ b/src/interfaces/trident-router.sol @@ -64,4 +64,17 @@ interface ITridentRouter { external payable returns (uint256 amountOut); + + function addLiquidity( + TokenInput[] calldata tokenInput, + address pool, + uint256 minLiquidity, + bytes calldata data + ) external payable returns (uint256 liquidity); +} + +interface ITridentPair { + function token0() external view returns (address); + + function token1() external view returns (address); } diff --git a/src/interfaces/univ3/IERC165.sol b/src/interfaces/univ3/IERC165.sol index c9b5a14ae..d7d8dadda 100644 --- a/src/interfaces/univ3/IERC165.sol +++ b/src/interfaces/univ3/IERC165.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12; /** * @dev Interface of the ERC165 standard, as defined in the diff --git a/src/interfaces/univ3/IERC721.sol b/src/interfaces/univ3/IERC721.sol index 6430c22e7..acbc7a8a5 100644 --- a/src/interfaces/univ3/IERC721.sol +++ b/src/interfaces/univ3/IERC721.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12; import "./IERC165.sol"; diff --git a/src/interfaces/univ3/ISwapRouter02.sol b/src/interfaces/univ3/ISwapRouter02.sol index 784f68072..e42c5716e 100644 --- a/src/interfaces/univ3/ISwapRouter02.sol +++ b/src/interfaces/univ3/ISwapRouter02.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.12; +pragma solidity >=0.6.12; pragma experimental ABIEncoderV2; /// @title Router token swapping functionality diff --git a/src/interfaces/univ3/IUniswapV3PositionsNFT.sol b/src/interfaces/univ3/IUniswapV3PositionsNFT.sol index ec34ac029..3c18e508f 100644 --- a/src/interfaces/univ3/IUniswapV3PositionsNFT.sol +++ b/src/interfaces/univ3/IUniswapV3PositionsNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.12; +pragma solidity >=0.6.12; pragma experimental ABIEncoderV2; import "./IERC721.sol"; diff --git a/src/interfaces/univ3/IUniswapV3Staker.sol b/src/interfaces/univ3/IUniswapV3Staker.sol index db9b2f934..2163de6cc 100644 --- a/src/interfaces/univ3/IUniswapV3Staker.sol +++ b/src/interfaces/univ3/IUniswapV3Staker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.12; +pragma solidity >=0.6.12; pragma experimental ABIEncoderV2; import "./IUniswapV3Pool.sol"; diff --git a/src/lib/BoringERC20.sol b/src/lib/BoringERC20.sol index cbe49ee7b..2e630eefb 100644 --- a/src/lib/BoringERC20.sol +++ b/src/lib/BoringERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.6.0; +pragma solidity >=0.6.0; import "./erc20.sol"; diff --git a/src/lib/context.sol b/src/lib/context.sol index 86a897786..85a5fc054 100644 --- a/src/lib/context.sol +++ b/src/lib/context.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT - pragma solidity >=0.6.0; /* diff --git a/src/lib/univ3/FullMath.sol b/src/lib/univ3/FullMath.sol index 0e58344bf..9985059e2 100644 --- a/src/lib/univ3/FullMath.sol +++ b/src/lib/univ3/FullMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.0; +pragma solidity >=0.4.0 <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 @@ -121,4 +121,4 @@ library FullMath { result++; } } -} \ No newline at end of file +} diff --git a/src/lib/univ3/LowGasSafeMath.sol b/src/lib/univ3/LowGasSafeMath.sol index d1c6700c6..977bcfdf8 100644 --- a/src/lib/univ3/LowGasSafeMath.sol +++ b/src/lib/univ3/LowGasSafeMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.12; +pragma solidity >=0.6.12; /// @title Optimized overflow and underflow safe math operations /// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost diff --git a/src/lib/univ3/PoolActions.sol b/src/lib/univ3/PoolActions.sol index 65b457a50..43b0f7ae3 100644 --- a/src/lib/univ3/PoolActions.sol +++ b/src/lib/univ3/PoolActions.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Unlicense -pragma solidity ^0.6.12; +pragma solidity >=0.6.12; pragma experimental ABIEncoderV2; import "../../interfaces/univ3/IUniswapV3Pool.sol"; diff --git a/src/lib/univ3/PoolVariables.sol b/src/lib/univ3/PoolVariables.sol index 22cef6a9c..5a9c39cde 100644 --- a/src/lib/univ3/PoolVariables.sol +++ b/src/lib/univ3/PoolVariables.sol @@ -17,8 +17,8 @@ library PoolVariables { struct Info { uint256 amount0Desired; uint256 amount1Desired; - uint256 amount0; - uint256 amount1; + uint256 amount0Accepted; + uint256 amount1Accepted; uint128 liquidity; int24 tickLower; int24 tickUpper; @@ -149,7 +149,7 @@ library PoolVariables { //Calc base ticks (cache.tickLower, cache.tickUpper) = baseTicks(currentTick, baseThreshold, tickSpacing); //Calc amounts of token0 and token1 that can be stored in base range - (cache.amount0, cache.amount1) = amountsForTicks( + (cache.amount0Accepted, cache.amount1Accepted) = amountsForTicks( pool, cache.amount0Desired, cache.amount1Desired, @@ -157,13 +157,13 @@ library PoolVariables { cache.tickUpper ); //Liquidity that can be stored in base range - cache.liquidity = liquidityForAmounts(pool, cache.amount0, cache.amount1, cache.tickLower, cache.tickUpper); + cache.liquidity = liquidityForAmounts(pool, cache.amount0Accepted, cache.amount1Accepted, cache.tickLower, cache.tickUpper); //Get imbalanced token bool zeroGreaterOne = amountsDirection( cache.amount0Desired, cache.amount1Desired, - cache.amount0, - cache.amount1 + cache.amount0Accepted, + cache.amount1Accepted ); //Calc new tick(upper or lower) for imbalanced token if (zeroGreaterOne) { diff --git a/src/lib/univ3/TickMath.sol b/src/lib/univ3/TickMath.sol index 31b7d638a..ee48fee54 100644 --- a/src/lib/univ3/TickMath.sol +++ b/src/lib/univ3/TickMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.0; +pragma solidity >=0.5.0 <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 @@ -202,4 +202,4 @@ library TickMath { tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; } -} \ No newline at end of file +} diff --git a/src/optimism/chainlinkKeeper.sol b/src/optimism/chainlinkKeeper.sol new file mode 100644 index 000000000..ed1c9aafd --- /dev/null +++ b/src/optimism/chainlinkKeeper.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./interfaces/chainlink/AutomationCompatibleInterface.sol"; +import "./interfaces/strategyv2.sol"; +import "./interfaces/univ3/pool/IUniswapV3PoolState.sol"; + +contract PickleRebalancingKeeper is AutomationCompatibleInterface { + address[] public strategies; + address public keeperRegistry = 0x75c0530885F385721fddA23C539AF3701d6183D4; + int24 public threshold = 10; + + address public governance; + bool public disabled = false; + + modifier onlyGovernance() { + require(msg.sender == governance, "!Governance"); + _; + } + + modifier whenNotDisabled() { + require(!disabled, "Disabled"); + _; + } + + constructor(address _governance) { + governance = _governance; + } + + function strategiesLength() external view returns(uint256 length) { + length = strategies.length; + } + + function setGovernance(address _governance) external onlyGovernance { + governance = _governance; + } + + function setKeeperRegistry(address _keeperRegistry) external onlyGovernance { + keeperRegistry = _keeperRegistry; + } + + function setThreshold(int24 _threshold) external onlyGovernance { + threshold = _threshold; + } + + function setDisabled(bool _disabled) external onlyGovernance { + disabled = _disabled; + } + + function addStrategies(address[] calldata _addresses) external onlyGovernance { + for (uint256 i = 0; i < _addresses.length; i++) { + require(!_search(_addresses[i]), "Address Already Watched"); + strategies.push(_addresses[i]); + } + } + + function removeStrategy(address _address) external onlyGovernance { + require(_search(_address), "Address Not Watched"); + + for (uint256 i = 0; i < strategies.length; i++) { + if (strategies[i] == _address) { + strategies[i] = strategies[strategies.length - 1]; + strategies.pop(); + break; + } + } + } + + function checkUpkeep(bytes calldata) + external + view + override + whenNotDisabled + returns (bool upkeepNeeded, bytes memory performData) + { + address[] memory _stratsToUpkeep = new address[](strategies.length); + + uint24 counter = 0; + for (uint256 i = 0; i < strategies.length; i++) { + bool shouldRebalance = _checkValidToCall(strategies[i]); + if (shouldRebalance == true) { + _stratsToUpkeep[counter] = strategies[i]; + upkeepNeeded = true; + counter++; + } + } + + if (upkeepNeeded == true) { + address[] memory stratsToUpkeep = new address[](counter); + for (uint256 i = 0; i < counter; i++) { + stratsToUpkeep[i] = _stratsToUpkeep[i]; + } + performData = abi.encode(stratsToUpkeep); + } + } + + function performUpkeep(bytes calldata performData) external override whenNotDisabled { + address[] memory stratsToUpkeep = abi.decode(performData, (address[])); + + for (uint24 i = 0; i < stratsToUpkeep.length; i++) { + require(_checkValidToCall(stratsToUpkeep[i]), "!Valid"); + IStrategyV2(stratsToUpkeep[i]).rebalance(); + } + } + + function _search(address _address) internal view returns (bool) { + for (uint256 i = 0; i < strategies.length; i++) { + if (strategies[i] == _address) { + return true; + } + } + return false; + } + + function _checkValidToCall(address _strategy) internal view returns (bool) { + require(_search(_strategy), "Address Not Watched"); + + int24 _lowerTick = IStrategyV2(_strategy).tick_lower(); + int24 _upperTick = IStrategyV2(_strategy).tick_upper(); + int24 _range = _upperTick - _lowerTick; + int24 _limitVar = _range / threshold; + int24 _lowerLimit = _lowerTick + _limitVar; + int24 _upperLimit = _upperTick - _limitVar; + + (, int24 _currentTick, , , , , ) = IUniswapV3PoolState(IStrategyV2(_strategy).pool()).slot0(); + if (_currentTick < _lowerLimit || _currentTick > _upperLimit) { + return true; + } + return false; + } +} diff --git a/src/optimism/interfaces/chainlink/AutomationCompatibleInterface.sol b/src/optimism/interfaces/chainlink/AutomationCompatibleInterface.sol new file mode 100644 index 000000000..e0899d427 --- /dev/null +++ b/src/optimism/interfaces/chainlink/AutomationCompatibleInterface.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface AutomationCompatibleInterface { + /** + * @notice method that is simulated by the keepers to see if any work actually + * needs to be performed. This method does does not actually need to be + * executable, and since it is only ever simulated it can consume lots of gas. + * @dev To ensure that it is never called, you may want to add the + * cannotExecute modifier from KeeperBase to your implementation of this + * method. + * @param checkData specified in the upkeep registration so it is always the + * same for a registered upkeep. This can easily be broken down into specific + * arguments using `abi.decode`, so multiple upkeeps can be registered on the + * same contract and easily differentiated by the contract. + * @return upkeepNeeded boolean to indicate whether the keeper should call + * performUpkeep or not. + * @return performData bytes that the keeper should call performUpkeep with, if + * upkeep is needed. If you would like to encode data to decode later, try + * `abi.encode`. + */ + function checkUpkeep(bytes calldata checkData) external returns (bool upkeepNeeded, bytes memory performData); + + /** + * @notice method that is actually executed by the keepers, via the registry. + * The data returned by the checkUpkeep simulation will be passed into + * this method to actually be executed. + * @dev The input to this method should not be trusted, and the caller of the + * method should not even be restricted to any single registry. Anyone should + * be able call it, and the input should be validated, there is no guarantee + * that the data passed in is the performData returned from checkUpkeep. This + * could happen due to malicious keepers, racing keepers, or simply a state + * change while the performUpkeep transaction is waiting for confirmation. + * Always validate the data passed in. + * @param performData is the data which was passed back from the checkData + * simulation. If it is encoded, it can easily be decoded into other types by + * calling `abi.decode`. This data should not be trusted, and should be + * validated against the contract's current state. + */ + function performUpkeep(bytes calldata performData) external; +} \ No newline at end of file diff --git a/src/optimism/interfaces/strategyv2.sol b/src/optimism/interfaces/strategyv2.sol new file mode 100644 index 000000000..d24098e06 --- /dev/null +++ b/src/optimism/interfaces/strategyv2.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +interface IStrategyV2 { + function tick_lower() external view returns (int24); + + function tick_upper() external view returns (int24); + + function balanceProportion(int24, int24) external; + + function pool() external view returns (address); + + function timelock() external view returns (address); + + function deposit() external; + + function withdraw(address) external; + + function withdraw(uint256) external returns (uint256, uint256); + + function withdrawAll() external returns (uint256, uint256); + + function liquidityOf() external view returns (uint256); + + function harvest() external; + + function rebalance() external; + + function setTimelock(address) external; + + function setController(address _controller) external; +} diff --git a/src/optimism/minichefProxy.sol b/src/optimism/minichefProxy.sol new file mode 100644 index 000000000..4a577a018 --- /dev/null +++ b/src/optimism/minichefProxy.sol @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +struct PoolInfo { + uint128 accPicklePerShare; + uint64 lastRewardTime; + uint64 allocPoint; +} + +interface IMiniChef { + function add( + uint256 allocPoint, + address _lpToken, + address _rewarder + ) external; + + function set( + uint256 _pid, + uint256 _allocPoint, + address _rewarder, + bool overwrite + ) external; + + function setPicklePerSecond(uint256 _picklePerSecond) external; + + function updatePool(uint256 pid) external returns (PoolInfo memory pool); + + function massUpdatePools(uint256[] calldata pids) external; + + function poolLength() external view returns (uint256 pools); + + function transferOwnership( + address newOwner, + bool direct, + bool renounce + ) external; + + function claimOwnership() external; +} + +interface IRewarder { + function setRewardPerSecond(uint256 _rewardPerSecond) external; + + function add(uint256 allocPoint, uint256 _pid) external; + + function set(uint256 _pid, uint256 _allocPoint) external; + + function updatePool(uint256 pid) external returns (PoolInfo memory pool); + + function massUpdatePools(uint256[] calldata pids) external; + + function transferOwnership( + address newOwner, + bool direct, + bool renounce + ) external; + + function claimOwnership() external; +} + +contract ChefProxy { + address public governance; + address public pendingGovernance; + + address[] public strategists; + mapping(address => bool) public isStrategist; + + IMiniChef public MINICHEF; + IRewarder public REWARDER; + + modifier onlyGovernance() { + require(msg.sender == governance, "!Governance"); + _; + } + + modifier onlyStrategist() { + require(isStrategist[msg.sender], "!Strategist"); + _; + } + + constructor(IMiniChef _minichef, IRewarder _rewarder) { + governance = msg.sender; + MINICHEF = _minichef; + REWARDER = _rewarder; + } + + function setPendingGovernance(address _newGovernance) external onlyGovernance { + pendingGovernance = _newGovernance; + } + + function claimGovernance() external { + require(msg.sender == pendingGovernance, "!pendingGovernance"); + governance = pendingGovernance; + pendingGovernance = address(0); + } + + function addStrategist(address _newStrategist) external onlyGovernance { + require(!isStrategist[msg.sender], "Already a strategist"); + + strategists.push(_newStrategist); + isStrategist[_newStrategist] = true; + } + + function removeStrategist(address _strategist) external onlyGovernance { + require(isStrategist[_strategist], "!Strategist"); + + for (uint256 i = 0; i < strategists.length; i++) { + if (strategists[i] == _strategist) { + strategists[i] = strategists[strategists.length - 1]; + strategists.pop(); + break; + } + } + isStrategist[_strategist] = false; + } + + function setMinichef(IMiniChef _newMinichef) external onlyGovernance { + MINICHEF = _newMinichef; + } + + function setRewarder(IRewarder _newRewarder) external onlyGovernance { + REWARDER = _newRewarder; + } + + ///@notice set an address as pendingOwner on the minichef + function transferMinichefOwnership(address _newOwner) external onlyGovernance { + MINICHEF.transferOwnership(_newOwner, false, false); + } + + ///@notice claims ownership of the minichef + function claimMinichefOwnership() external onlyGovernance { + MINICHEF.claimOwnership(); + } + + ///@notice set an address as pendingOwner on the rewarder + function transferRewarderOwnership(address _newOwner) external onlyGovernance { + REWARDER.transferOwnership(_newOwner, false, false); + } + + ///@notice claims ownership of the rewarder + function claimRewarderOwnership() external onlyGovernance { + REWARDER.claimOwnership(); + } + + function setPicklePerSecond(uint256 _picklePerSecond) external onlyStrategist { + MINICHEF.setPicklePerSecond(_picklePerSecond); + } + + function setRewardPerSecond(uint256 _rewardPerSecond) external onlyStrategist { + REWARDER.setRewardPerSecond(_rewardPerSecond); + } + + ///@notice Add multiple LPs to minichef and rewarder + function add(address[] calldata _lpTokens, uint256[] calldata _allocPoints) external onlyStrategist { + require(_lpTokens.length == _allocPoints.length, "!match"); + + uint256 poolLength = MINICHEF.poolLength(); + + for (uint256 i = 0; i < _lpTokens.length; i++) { + MINICHEF.add(_allocPoints[i], _lpTokens[i], address(REWARDER)); + REWARDER.add(_allocPoints[i], poolLength + i); + } + } + + ///@notice Update the allocPoints for multiple pools on minichef and rewarder + function set(uint256[] calldata _pids, uint256[] calldata _allocPoints) external onlyStrategist { + require(_pids.length == _allocPoints.length, "!match"); + + for (uint256 i = 0; i < _pids.length; i++) { + MINICHEF.set(_pids[i], _allocPoints[i], address(REWARDER), false); + REWARDER.set(_pids[i], _allocPoints[i]); + } + } + + ///@notice An emergency function for the governance to execute calls that are not supported by this contract (e.g, renounce chef ownership to address(0)) + function execute( + address target, + string calldata signature, + bytes calldata data + ) external onlyGovernance returns (bytes memory returnData) { + bytes memory callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); + + bool success; + (success, returnData) = target.call(callData); + require(success, "execute failed"); + } +} diff --git a/src/optimism/pickle-jar-univ3.sol b/src/optimism/pickle-jar-univ3.sol deleted file mode 100644 index a9f57ee7a..000000000 --- a/src/optimism/pickle-jar-univ3.sol +++ /dev/null @@ -1,390 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.12; -pragma experimental ABIEncoderV2; - -// ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── -// ─██████████████─██████████─██████████████─██████──████████─██████─────────██████████████────────────██████─██████████████─████████████████─── -// ─██░░░░░░░░░░██─██░░░░░░██─██░░░░░░░░░░██─██░░██──██░░░░██─██░░██─────────██░░░░░░░░░░██────────────██░░██─██░░░░░░░░░░██─██░░░░░░░░░░░░██─── -// ─██░░██████░░██─████░░████─██░░██████████─██░░██──██░░████─██░░██─────────██░░██████████────────────██░░██─██░░██████░░██─██░░████████░░██─── -// ─██░░██──██░░██───██░░██───██░░██─────────██░░██──██░░██───██░░██─────────██░░██────────────────────██░░██─██░░██──██░░██─██░░██────██░░██─── -// ─██░░██████░░██───██░░██───██░░██─────────██░░██████░░██───██░░██─────────██░░██████████────────────██░░██─██░░██████░░██─██░░████████░░██─── -// ─██░░░░░░░░░░██───██░░██───██░░██─────────██░░░░░░░░░░██───██░░██─────────██░░░░░░░░░░██────────────██░░██─██░░░░░░░░░░██─██░░░░░░░░░░░░██─── -// ─██░░██████████───██░░██───██░░██─────────██░░██████░░██───██░░██─────────██░░██████████────██████──██░░██─██░░██████░░██─██░░██████░░████─── -// ─██░░██───────────██░░██───██░░██─────────██░░██──██░░██───██░░██─────────██░░██────────────██░░██──██░░██─██░░██──██░░██─██░░██──██░░██───── -// ─██░░██─────────████░░████─██░░██████████─██░░██──██░░████─██░░██████████─██░░██████████────██░░██████░░██─██░░██──██░░██─██░░██──██░░██████─ -// ─██░░██─────────██░░░░░░██─██░░░░░░░░░░██─██░░██──██░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██────██░░░░░░░░░░██─██░░██──██░░██─██░░██──██░░░░░░██─ -// ─██████─────────██████████─██████████████─██████──████████─██████████████─██████████████────██████████████─██████──██████─██████──██████████─ -// ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - -import "../interfaces/controllerv2.sol"; -import "../lib/erc20.sol"; -import "../lib/univ3/PoolActions.sol"; -import "../lib/reentrancy-guard.sol"; -import "../lib/safe-math.sol"; -import "../interfaces/univ3/IUniswapV3PositionsNFT.sol"; -import "../interfaces/univ3/IUniswapV3Pool.sol"; -import "./interfaces/univ3/ISwapRouter.sol"; -import "../interfaces/weth.sol"; - -contract PickleJarUniV3Optimism is ERC20, ReentrancyGuard { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - using SafeMath for uint128; - using PoolVariables for IUniswapV3Pool; - - address public constant weth = 0x4200000000000000000000000000000000000006; - address public constant univ3Router = - 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; - - address public governance; - address public timelock; - address public controller; - - bool public paused; - - IUniswapV3Pool public pool; - - IERC20 public token0; - IERC20 public token1; - - constructor( - string memory _name, - string memory _symbol, - address _pool, - address _governance, - address _timelock, - address _controller - ) public ERC20(_name, _symbol) { - pool = IUniswapV3Pool(_pool); - token0 = IERC20(pool.token0()); - token1 = IERC20(pool.token1()); - - governance = _governance; - timelock = _timelock; - controller = _controller; - paused = false; - } - - function totalLiquidity() public view returns (uint256) { - return - liquidityOfThis().add( - IControllerV2(controller).liquidityOf(address(pool)) - ); - } - - function liquidityOfThis() public view returns (uint256) { - uint256 _balance0 = token0.balanceOf(address(this)); - uint256 _balance1 = token1.balanceOf(address(this)); - return - uint256( - pool.liquidityForAmounts( - _balance0, - _balance1, - getLowerTick(), - getUpperTick() - ) - ); - } - - function getUpperTick() public view returns (int24) { - return IControllerV2(controller).getUpperTick(address(pool)); - } - - function getLowerTick() public view returns (int24) { - return IControllerV2(controller).getLowerTick(address(pool)); - } - - function setGovernance(address _governance) public { - require(msg.sender == governance, "!governance"); - governance = _governance; - } - - function setTimelock(address _timelock) public { - require(msg.sender == timelock, "!timelock"); - timelock = _timelock; - } - - function setController(address _controller) public { - require(msg.sender == timelock, "!timelock"); - controller = _controller; - } - - event JarPaused(uint256 block, uint256 timestamp); - - function setPaused(bool _paused) external { - require(msg.sender == governance, "!governance"); - paused = _paused; - emit JarPaused(block.number, block.timestamp); - } - - function earn() public { - require(liquidityOfThis() > 0, "no liquidity here"); - - uint256 balance0 = token0.balanceOf(address(this)); - uint256 balance1 = token1.balanceOf(address(this)); - - token0.safeTransfer(controller, balance0); - token1.safeTransfer(controller, balance1); - - IControllerV2(controller).earn(address(pool), balance0, balance1); - } - - function deposit(uint256 token0Amount, uint256 token1Amount) - external - payable - nonReentrant - whenNotPaused - { - bool _ethUsed; - (token0Amount, token1Amount, _ethUsed) = _convertEth( - token0Amount, - token1Amount - ); - - _deposit(token0Amount, token1Amount); - - uint256 _liquidity = _refundUnused(_ethUsed); - - uint256 shares = 0; - if (totalSupply() == 0) { - shares = _liquidity; - } else { - shares = (_liquidity.mul(totalSupply())).div( - IControllerV2(controller).liquidityOf(address(pool)) - ); - } - - _mint(msg.sender, shares); - - earn(); - } - - function getProportion() public view returns (uint256) { - (uint256 a1, uint256 a2) = pool.amountsForLiquidity( - 1e18, - getLowerTick(), - getUpperTick() - ); - return (a2 * (10**18)) / a1; - } - - function withdrawAll() external { - withdraw(balanceOf(msg.sender)); - } - - function withdraw(uint256 _shares) public nonReentrant whenNotPaused { - uint256 r = (totalLiquidity().mul(_shares)).div(totalSupply()); - (uint256 _expectA0, uint256 _expectA1) = pool.amountsForLiquidity( - uint128(r), - getLowerTick(), - getUpperTick() - ); - _burn(msg.sender, _shares); - // Check balance - uint256[2] memory _balances = [ - token0.balanceOf(address(this)), - token1.balanceOf(address(this)) - ]; - uint256 b = liquidityOfThis(); - - if (b < r) { - uint256 _withdraw = r.sub(b); - (uint256 _a0, uint256 _a1) = IControllerV2(controller).withdraw( - address(pool), - _withdraw - ); - _expectA0 = _balances[0].add(_a0); - _expectA1 = _balances[1].add(_a1); - } - - token0.safeTransfer(msg.sender, _expectA0); - token1.safeTransfer(msg.sender, _expectA1); - } - - function getRatio() public view returns (uint256) { - if (totalSupply() == 0) return 0; - return totalLiquidity().mul(1e18).div(totalSupply()); - } - - function _deposit(uint256 token0Amount, uint256 token1Amount) internal { - if ( - !(token0.balanceOf(address(this)) >= token0Amount) && - (token0Amount != 0) - ) token0.safeTransferFrom(msg.sender, address(this), token0Amount); - - if ( - !(token1.balanceOf(address(this)) >= token1Amount) && - (token1Amount != 0) - ) token1.safeTransferFrom(msg.sender, address(this), token1Amount); - - _balanceProportion(getLowerTick(), getUpperTick()); - } - - function _convertEth(uint256 token0Amount, uint256 token1Amount) - internal - returns ( - uint256, - uint256, - bool - ) - { - bool _ethUsed = false; - uint256 _eth = msg.value; - if (_eth > 0) { - WETH(weth).deposit{value: _eth}(); - - if (address(token0) == weth) { - token0Amount = _eth; - _ethUsed = true; - } else if (address(token1) == weth) { - token1Amount = _eth; - _ethUsed = true; - } - } - return (token0Amount, token1Amount, _ethUsed); - } - - function _refundEth(uint256 _refund) internal { - WETH(weth).withdraw(_refund); - (bool sent, bytes memory data) = (msg.sender).call{value: _refund}(""); - require(sent, "Failed to refund Eth"); - } - - function _refundUnused(bool _ethUsed) internal returns (uint256) { - PoolVariables.Info memory _cache; - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(getLowerTick()); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(getUpperTick()); - - _cache.liquidity = uint128( - LiquidityAmounts - .getLiquidityForAmount0( - sqrtRatioAX96, - sqrtRatioBX96, - _cache.amount0Desired - ).add( - LiquidityAmounts.getLiquidityForAmount1( - sqrtRatioAX96, - sqrtRatioBX96, - _cache.amount1Desired - ) - ) - ); - - (_cache.amount0, _cache.amount1) = LiquidityAmounts - .getAmountsForLiquidity( - sqrtPriceX96, - sqrtRatioAX96, - sqrtRatioBX96, - _cache.liquidity - ); - - if (_cache.amount0Desired > _cache.amount0) - if ((address(token0) == address(weth)) && _ethUsed) - _refundEth(_cache.amount0Desired.sub(_cache.amount0)); - else { - token0.safeTransfer( - msg.sender, - _cache.amount0Desired.sub(_cache.amount0) - ); - } - - if (_cache.amount1Desired > _cache.amount1) - if ((address(token1) == address(weth)) && _ethUsed) - _refundEth(_cache.amount1Desired.sub(_cache.amount1)); - else { - token1.safeTransfer( - msg.sender, - _cache.amount1Desired.sub(_cache.amount1) - ); - } - return _cache.liquidity; - } - - function _balanceProportion(int24 _tickLower, int24 _tickUpper) internal { - PoolVariables.Info memory _cache; - - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(_tickLower); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(_tickUpper); - - _cache.liquidity = uint128( - LiquidityAmounts - .getLiquidityForAmount0( - sqrtRatioAX96, - sqrtRatioBX96, - _cache.amount0Desired - ).add( - LiquidityAmounts.getLiquidityForAmount1( - sqrtRatioAX96, - sqrtRatioBX96, - _cache.amount1Desired - ) - ) - ); - - (_cache.amount0, _cache.amount1) = LiquidityAmounts - .getAmountsForLiquidity( - sqrtPriceX96, - sqrtRatioAX96, - sqrtRatioBX96, - _cache.liquidity - ); - - //Determine Trade Direction - bool _zeroForOne = _cache.amount0Desired > _cache.amount0 - ? true - : false; - - //Determine Amount to swap - uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0)) - : (_cache.amount1Desired.sub(_cache.amount1)); - - if (_amountSpecified > 0) { - //Determine Token to swap - address _inputToken = _zeroForOne - ? address(token0) - : address(token1); - - IERC20(_inputToken).safeApprove(univ3Router, 0); - IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); - - //Swap the token imbalanced - ISwapRouter(univ3Router).exactInputSingle( - ISwapRouter.ExactInputSingleParams({ - tokenIn: _inputToken, - tokenOut: _zeroForOne ? address(token1) : address(token0), - fee: pool.fee(), - recipient: address(this), - amountIn: _amountSpecified, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }) - ); - } - } - - modifier whenNotPaused() { - require(paused == false, "paused"); - _; - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC721Received.selector; - } - - fallback() external payable {} -} diff --git a/src/pickle-jar-univ3.sol b/src/pickle-jar-univ3.sol index d3634df9b..39be5983e 100644 --- a/src/pickle-jar-univ3.sol +++ b/src/pickle-jar-univ3.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; @@ -24,6 +25,7 @@ import "./interfaces/univ3/IUniswapV3PositionsNFT.sol"; import "./interfaces/univ3/IUniswapV3Pool.sol"; import "./interfaces/univ3/ISwapRouter02.sol"; import "./interfaces/weth.sol"; +import "./interfaces/strategy-univ3.sol"; contract PickleJarUniV3 is ERC20, ReentrancyGuard { using SafeERC20 for IERC20; @@ -32,9 +34,7 @@ contract PickleJarUniV3 is ERC20, ReentrancyGuard { using SafeMath for uint128; using PoolVariables for IUniswapV3Pool; - address public constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - address public constant univ3Router = - 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; + address public immutable native; address public governance; address public timelock; @@ -51,10 +51,12 @@ contract PickleJarUniV3 is ERC20, ReentrancyGuard { string memory _name, string memory _symbol, address _pool, + address _native, address _governance, address _timelock, address _controller ) public ERC20(_name, _symbol) { + native = _native; pool = IUniswapV3Pool(_pool); token0 = IERC20(pool.token0()); token1 = IERC20(pool.token1()); @@ -66,24 +68,13 @@ contract PickleJarUniV3 is ERC20, ReentrancyGuard { } function totalLiquidity() public view returns (uint256) { - return - liquidityOfThis().add( - IControllerV2(controller).liquidityOf(address(pool)) - ); + return liquidityOfThis().add(IControllerV2(controller).liquidityOf(address(pool))); } function liquidityOfThis() public view returns (uint256) { uint256 _balance0 = token0.balanceOf(address(this)); uint256 _balance1 = token1.balanceOf(address(this)); - return - uint256( - pool.liquidityForAmounts( - _balance0, - _balance1, - getLowerTick(), - getUpperTick() - ) - ); + return uint256(pool.liquidityForAmounts(_balance0, _balance1, getLowerTick(), getUpperTick())); } function getUpperTick() public view returns (int24) { @@ -129,89 +120,44 @@ contract PickleJarUniV3 is ERC20, ReentrancyGuard { IControllerV2(controller).earn(address(pool), balance0, balance1); } - function deposit( - uint256 token0Amount, - uint256 token1Amount, - bool _swap - ) external payable nonReentrant whenNotPaused { + function deposit(uint256 token0Amount, uint256 token1Amount) external payable nonReentrant whenNotPaused { bool _ethUsed; - (token0Amount, token1Amount, _ethUsed) = _convertEth( - token0Amount, - token1Amount - ); + (token0Amount, token1Amount, _ethUsed) = _convertEth(token0Amount, token1Amount); + + uint256 _totalLiquidity = IControllerV2(controller).liquidityOf(address(pool)); - uint256 _liquidity; - _swap - ? _liquidity = _depositSwap(token0Amount, token1Amount, _ethUsed) - : _liquidity = _depositExact(token0Amount, token1Amount, _ethUsed); + uint256 _liquidity = _depositAndRefundUnused(_ethUsed, token0Amount, token1Amount); uint256 shares = 0; if (totalSupply() == 0) { shares = _liquidity; } else { - shares = (_liquidity.mul(totalSupply())).div( - IControllerV2(controller).liquidityOf(address(pool)) - ); + shares = (_liquidity.mul(totalSupply())).div(_totalLiquidity); } _mint(msg.sender, shares); - - earn(); } function getProportion() public view returns (uint256) { - (uint256 a1, uint256 a2) = pool.amountsForLiquidity( - 1e18, - getLowerTick(), - getUpperTick() - ); + (uint256 a1, uint256 a2) = pool.amountsForLiquidity(1e18, getLowerTick(), getUpperTick()); return (a2 * (10**18)) / a1; } - function _getCorrectAmounts(uint256 _token0Amount, uint256 _token1Amount) - internal - returns (uint256, uint256) - { - uint256 amount0ForAmount1 = _token1Amount.mul(1e18).div( - getProportion() - ); - uint256 amount1ForAmount0 = _token0Amount.mul(getProportion()).div( - 1e18 - ); - - if (_token0Amount > amount0ForAmount1) { - _token0Amount = amount0ForAmount1; - } else { - _token1Amount = amount1ForAmount0; - } - return (_token0Amount, _token1Amount); - } - function withdrawAll() external { withdraw(balanceOf(msg.sender)); } function withdraw(uint256 _shares) public nonReentrant whenNotPaused { uint256 r = (totalLiquidity().mul(_shares)).div(totalSupply()); - (uint256 _expectA0, uint256 _expectA1) = pool.amountsForLiquidity( - uint128(r), - getLowerTick(), - getUpperTick() - ); + (uint256 _expectA0, uint256 _expectA1) = pool.amountsForLiquidity(uint128(r), getLowerTick(), getUpperTick()); _burn(msg.sender, _shares); // Check balance - uint256[2] memory _balances = [ - token0.balanceOf(address(this)), - token1.balanceOf(address(this)) - ]; + uint256[2] memory _balances = [token0.balanceOf(address(this)), token1.balanceOf(address(this))]; uint256 b = liquidityOfThis(); if (b < r) { uint256 _withdraw = r.sub(b); - (uint256 _a0, uint256 _a1) = IControllerV2(controller).withdraw( - address(pool), - _withdraw - ); + (uint256 _a0, uint256 _a1) = IControllerV2(controller).withdraw(address(pool), _withdraw); _expectA0 = _balances[0].add(_a0); _expectA1 = _balances[1].add(_a1); } @@ -225,62 +171,6 @@ contract PickleJarUniV3 is ERC20, ReentrancyGuard { return totalLiquidity().mul(1e18).div(totalSupply()); } - function _depositSwap( - uint256 token0Amount, - uint256 token1Amount, - bool _ethUsed - ) internal returns (uint256) { - if ( - !(token0.balanceOf(address(this)) >= token0Amount) && - (token0Amount != 0) - ) token0.safeTransferFrom(msg.sender, address(this), token0Amount); - - if ( - !(token1.balanceOf(address(this)) >= token1Amount) && - (token1Amount != 0) - ) token1.safeTransferFrom(msg.sender, address(this), token1Amount); - - _balanceProportion(getLowerTick(), getUpperTick()); - - return _refundUnused(_ethUsed); - } - - function _depositExact( - uint256 _token0AmountDesired, - uint256 _token1AmountDesired, - bool _ethUsed - ) internal returns (uint256) { - (uint256 _token0Amount, uint256 _token1Amount) = _getCorrectAmounts( - _token0AmountDesired, - _token1AmountDesired - ); - - if (_token0Amount > 0) - token0.safeTransferFrom(msg.sender, address(this), _token0Amount); - if (_token1Amount > 0) - token1.safeTransferFrom(msg.sender, address(this), _token1Amount); - - if (_ethUsed) { - if ((address(token0) == address(weth))) { - uint256 refundAmt = _token0AmountDesired.sub(_token0Amount); - if (refundAmt > 0) _refundEth(refundAmt); - } - - if ((address(token1) == address(weth))) { - uint256 refundAmt = _token1AmountDesired.sub(_token1Amount); - if (refundAmt > 0) _refundEth(refundAmt); - } - } - - return - pool.liquidityForAmounts( - _token0Amount, - _token1Amount, - getLowerTick(), - getUpperTick() - ); - } - function _convertEth(uint256 token0Amount, uint256 token1Amount) internal returns ( @@ -292,12 +182,12 @@ contract PickleJarUniV3 is ERC20, ReentrancyGuard { bool _ethUsed = false; uint256 _eth = msg.value; if (_eth > 0) { - WETH(weth).deposit{value: _eth}(); + WETH(native).deposit{value: _eth}(); - if (address(token0) == weth) { + if (address(token0) == native) { token0Amount = _eth; _ethUsed = true; - } else if (address(token1) == weth) { + } else if (address(token1) == native) { token1Amount = _eth; _ethUsed = true; } @@ -306,129 +196,43 @@ contract PickleJarUniV3 is ERC20, ReentrancyGuard { } function _refundEth(uint256 _refund) internal { - WETH(weth).withdraw(_refund); - (bool sent, bytes memory data) = (msg.sender).call{value: _refund}(""); + WETH(native).withdraw(_refund); + (bool sent, ) = (msg.sender).call{value: _refund}(""); require(sent, "Failed to refund Eth"); } - function _refundUnused(bool _ethUsed) internal returns (uint256) { - PoolVariables.Info memory _cache; - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(getLowerTick()); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(getUpperTick()); - - _cache.liquidity = uint128( - LiquidityAmounts - .getLiquidityForAmount0( - sqrtRatioAX96, - sqrtRatioBX96, - _cache.amount0Desired - ).add( - LiquidityAmounts.getLiquidityForAmount1( - sqrtRatioAX96, - sqrtRatioBX96, - _cache.amount1Desired - ) - ) - ); - - (_cache.amount0, _cache.amount1) = LiquidityAmounts - .getAmountsForLiquidity( - sqrtPriceX96, - sqrtRatioAX96, - sqrtRatioBX96, - _cache.liquidity - ); + function _depositAndRefundUnused( + bool _ethUsed, + uint256 token0Amount, + uint256 token1Amount + ) internal returns (uint256 liquidity) { + if (token0Amount != 0 && (!_ethUsed || address(token0) != native)) + token0.safeTransferFrom(msg.sender, address(this), token0Amount); - if (_cache.amount0Desired > _cache.amount0) - if ((address(token0) == address(weth)) && _ethUsed) - _refundEth(_cache.amount0Desired.sub(_cache.amount0)); - else { - token0.safeTransfer( - msg.sender, - _cache.amount0Desired.sub(_cache.amount0) - ); - } + if (token1Amount != 0 && (!_ethUsed || address(token1) != native)) + token1.safeTransferFrom(msg.sender, address(this), token1Amount); - if (_cache.amount1Desired > _cache.amount1) - if ((address(token1) == address(weth)) && _ethUsed) - _refundEth(_cache.amount1Desired.sub(_cache.amount1)); - else { - token1.safeTransfer( - msg.sender, - _cache.amount1Desired.sub(_cache.amount1) - ); - } - return _cache.liquidity; - } + address strategy = IControllerV2(controller).strategies(address(pool)); - function _balanceProportion(int24 _tickLower, int24 _tickUpper) internal { - PoolVariables.Info memory _cache; - - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(_tickLower); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(_tickUpper); - - _cache.liquidity = uint128( - LiquidityAmounts - .getLiquidityForAmount0( - sqrtRatioAX96, - sqrtRatioBX96, - _cache.amount0Desired - ).add( - LiquidityAmounts.getLiquidityForAmount1( - sqrtRatioAX96, - sqrtRatioBX96, - _cache.amount1Desired - ) - ) - ); + token0.safeApprove(strategy, 0); + token0.safeApprove(strategy, token0Amount); + token1.safeApprove(strategy, 0); + token1.safeApprove(strategy, token1Amount); - (_cache.amount0, _cache.amount1) = LiquidityAmounts - .getAmountsForLiquidity( - sqrtPriceX96, - sqrtRatioAX96, - sqrtRatioBX96, - _cache.liquidity + uint256 unusedAmount0; + uint256 unusedAmount1; + (liquidity, unusedAmount0, unusedAmount1) = IStrategyUniV3(strategy).balanceAndDeposit( + token0Amount, + token1Amount ); - //Determine Trade Direction - bool _zeroForOne = _cache.amount0Desired > _cache.amount0 - ? true - : false; - - //Determine Amount to swap - uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0)) - : (_cache.amount1Desired.sub(_cache.amount1)); - - if (_amountSpecified > 0) { - //Determine Token to swap - address _inputToken = _zeroForOne - ? address(token0) - : address(token1); - - IERC20(_inputToken).safeApprove(univ3Router, 0); - IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); - - //Swap the token imbalanced - ISwapRouter02(univ3Router).exactInputSingle( - ISwapRouter02.ExactInputSingleParams({ - tokenIn: _inputToken, - tokenOut: _zeroForOne ? address(token1) : address(token0), - fee: pool.fee(), - recipient: address(this), - amountIn: _amountSpecified, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }) - ); + if ((address(token0) == address(native)) && _ethUsed) _refundEth(unusedAmount0); + else { + token0.safeTransfer(msg.sender, unusedAmount0); + } + if ((address(token1) == address(native)) && _ethUsed) _refundEth(unusedAmount1); + else { + token1.safeTransfer(msg.sender, unusedAmount1); } } @@ -446,5 +250,7 @@ contract PickleJarUniV3 is ERC20, ReentrancyGuard { return this.onERC721Received.selector; } + receive() external payable {} + fallback() external payable {} } diff --git a/src/polygon/interfaces/univ3/IERC165.sol b/src/polygon/interfaces/univ3/IERC165.sol deleted file mode 100644 index c9b5a14ae..000000000 --- a/src/polygon/interfaces/univ3/IERC165.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.6.12; - -/** - * @dev Interface of the ERC165 standard, as defined in the - * https://eips.ethereum.org/EIPS/eip-165[EIP]. - * - * Implementers can declare support of contract interfaces, which can then be - * queried by others ({ERC165Checker}). - * - * For an implementation, see {ERC165}. - */ -interface IERC165 { - /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] - * to learn more about how these ids are created. - * - * This function call must use less than 30 000 gas. - */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} diff --git a/src/polygon/interfaces/univ3/IERC721.sol b/src/polygon/interfaces/univ3/IERC721.sol deleted file mode 100644 index 6430c22e7..000000000 --- a/src/polygon/interfaces/univ3/IERC721.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.6.12; - -import "./IERC165.sol"; - -/** - * @dev Required interface of an ERC721 compliant contract. - */ -interface IERC721 is IERC165 { - /** - * @dev Emitted when `tokenId` token is transferred from `from` to `to`. - */ - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenId - ); - - /** - * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. - */ - event Approval( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - /** - * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. - */ - event ApprovalForAll( - address indexed owner, - address indexed operator, - bool approved - ); - - /** - * @dev Returns the number of tokens in ``owner``'s account. - */ - function balanceOf(address owner) external view returns (uint256 balance); - - /** - * @dev Returns the owner of the `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function ownerOf(uint256 tokenId) external view returns (address owner); - - /** - * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients - * are aware of the ERC721 protocol to prevent tokens from being forever locked. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * - * Emits a {Transfer} event. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) external; - - /** - * @dev Transfers `tokenId` token from `from` to `to`. - * - * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. - * - * Emits a {Transfer} event. - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) external; - - /** - * @dev Gives permission to `to` to transfer `tokenId` token to another account. - * The approval is cleared when the token is transferred. - * - * Only a single account can be approved at a time, so approving the zero address clears previous approvals. - * - * Requirements: - * - * - The caller must own the token or be an approved operator. - * - `tokenId` must exist. - * - * Emits an {Approval} event. - */ - function approve(address to, uint256 tokenId) external; - - /** - * @dev Returns the account approved for `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function getApproved(uint256 tokenId) - external - view - returns (address operator); - - /** - * @dev Approve or remove `operator` as an operator for the caller. - * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. - * - * Requirements: - * - * - The `operator` cannot be the caller. - * - * Emits an {ApprovalForAll} event. - */ - function setApprovalForAll(address operator, bool _approved) external; - - /** - * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. - * - * See {setApprovalForAll} - */ - function isApprovedForAll(address owner, address operator) - external - view - returns (bool); - - /** - * @dev Safely transfers `tokenId` token from `from` to `to`. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * - * Emits a {Transfer} event. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes calldata data - ) external; -} diff --git a/src/polygon/interfaces/univ3/ISwapRouter.sol b/src/polygon/interfaces/univ3/ISwapRouter.sol deleted file mode 100644 index e27d27e30..000000000 --- a/src/polygon/interfaces/univ3/ISwapRouter.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.6.7; -pragma experimental ABIEncoderV2; - -interface ISwapRouter { - struct ExactInputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; - address recipient; - uint256 amountIn; - uint256 amountOutMinimum; - uint160 sqrtPriceLimitX96; - } - - struct ExactInputParams { - bytes path; - address recipient; - uint256 deadline; - uint256 amountIn; - uint256 amountOutMinimum; - } - - function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); - - function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); -} - -interface IUniswapV3Factory { - function getPool( - address tokenA, - address tokenB, - uint24 fee - ) external view returns (address pool); -} diff --git a/src/polygon/interfaces/univ3/IUniswapV3Pool.sol b/src/polygon/interfaces/univ3/IUniswapV3Pool.sol deleted file mode 100644 index 87806faf9..000000000 --- a/src/polygon/interfaces/univ3/IUniswapV3Pool.sol +++ /dev/null @@ -1,20 +0,0 @@ -// 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"; - -/// @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 -{ - function fee() external view returns (uint24); -} diff --git a/src/polygon/interfaces/univ3/IUniswapV3PositionsNFT.sol b/src/polygon/interfaces/univ3/IUniswapV3PositionsNFT.sol deleted file mode 100644 index ec34ac029..000000000 --- a/src/polygon/interfaces/univ3/IUniswapV3PositionsNFT.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "./IERC721.sol"; - -// Originally INonfungiblePositionManager -interface IUniswapV3PositionsNFT is IERC721 { - struct CollectParams { - uint256 tokenId; - address recipient; - uint128 amount0Max; - uint128 amount1Max; - } - - struct MintParams { - address token0; - address token1; - uint24 fee; - int24 tickLower; - int24 tickUpper; - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0Min; - uint256 amount1Min; - address recipient; - uint256 deadline; - } - - struct IncreaseLiquidityParams { - uint256 tokenId; - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0Min; - uint256 amount1Min; - uint256 deadline; - } - - /// @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, // [0] - address operator, // [1] - address token0, // [2] - address token1, // [3] - uint24 fee, // [4] - int24 tickLower, // [5] - int24 tickUpper, // [6] - uint128 liquidity, // [7] - uint256 feeGrowthInside0LastX128, // [8] - uint256 feeGrowthInside1LastX128, // [9] - uint128 tokensOwed0, // [10] - uint128 tokensOwed1 // [11] - ); - - 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); - - /// @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); - - function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); - - function mint(MintParams calldata params) - external - payable - returns ( - uint256 tokenId, - uint128 liquidity, - uint256 amount0, - uint256 amount1 - ); - - function burn(uint256 tokenId) external payable; - - function refundETH() external payable; - - function unwrapWETH9(uint256 amountMinimum, address recipient) external payable; - - function sweepToken( - address token, - uint256 amountMinimum, - address recipient - ) external payable; -} diff --git a/src/polygon/interfaces/univ3/IUniswapV3Staker.sol b/src/polygon/interfaces/univ3/IUniswapV3Staker.sol deleted file mode 100644 index db9b2f934..000000000 --- a/src/polygon/interfaces/univ3/IUniswapV3Staker.sol +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "./IUniswapV3Pool.sol"; - -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); -} - -/// @title Uniswap V3 Staker Interface -/// @notice Allows staking nonfungible liquidity tokens in exchange for reward tokens -interface IUniswapV3Staker { - /// @param rewardToken The token being distributed as a reward - /// @param pool The Uniswap V3 pool - /// @param startTime The time when the incentive program begins - /// @param endTime The time when rewards stop accruing - /// @param refundee The address which receives any remaining reward tokens when the incentive is ended - struct IncentiveKey { - IERC20Minimal rewardToken; - IUniswapV3Pool pool; - uint256 startTime; - uint256 endTime; - address refundee; - } - - /// @notice The max duration of an incentive in seconds - function maxIncentiveDuration() external view returns (uint256); - - /// @notice The max amount of seconds into the future the incentive startTime can be set - function maxIncentiveStartLeadTime() external view returns (uint256); - - /// @notice Represents a staking incentive - /// @param incentiveId The ID of the incentive computed from its parameters - /// @return totalRewardUnclaimed The amount of reward token not yet claimed by users - /// @return totalSecondsClaimedX128 Total liquidity-seconds claimed, represented as a UQ32.128 - /// @return numberOfStakes The count of deposits that are currently staked for the incentive - function incentives(bytes32 incentiveId) - external - view - returns ( - uint256 totalRewardUnclaimed, - uint160 totalSecondsClaimedX128, - uint96 numberOfStakes - ); - - /// @notice Returns information about a deposited NFT - /// @return owner The owner of the deposited NFT - /// @return numberOfStakes Counter of how many incentives for which the liquidity is staked - /// @return tickLower The lower tick of the range - /// @return tickUpper The upper tick of the range - function deposits(uint256 tokenId) - external - view - returns ( - address owner, - uint48 numberOfStakes, - int24 tickLower, - int24 tickUpper - ); - - /// @notice Returns information about a staked liquidity NFT - /// @param tokenId The ID of the staked token - /// @param incentiveId The ID of the incentive for which the token is staked - /// @return secondsPerLiquidityInsideInitialX128 secondsPerLiquidity represented as a UQ32.128 - /// @return liquidity The amount of liquidity in the NFT as of the last time the rewards were computed - function stakes(uint256 tokenId, bytes32 incentiveId) - external - view - returns (uint160 secondsPerLiquidityInsideInitialX128, uint128 liquidity); - - /// @notice Returns amounts of reward tokens owed to a given address according to the last time all stakes were updated - /// @param rewardToken The token for which to check rewards - /// @param owner The owner for which the rewards owed are checked - /// @return rewardsOwed The amount of the reward token claimable by the owner - function rewards(IERC20Minimal rewardToken, address owner) external view returns (uint256 rewardsOwed); - - /// @notice Creates a new liquidity mining incentive program - /// @param key Details of the incentive to create - /// @param reward The amount of reward tokens to be distributed - function createIncentive(IncentiveKey memory key, uint256 reward) external; - - /// @notice Ends an incentive after the incentive end time has passed and all stakes have been withdrawn - /// @param key Details of the incentive to end - /// @return refund The remaining reward tokens when the incentive is ended - function endIncentive(IncentiveKey memory key) external returns (uint256 refund); - - /// @notice Transfers ownership of a deposit from the sender to the given recipient - /// @param tokenId The ID of the token (and the deposit) to transfer - /// @param to The new owner of the deposit - function transferDeposit(uint256 tokenId, address to) external; - - /// @notice Withdraws a Uniswap V3 LP token `tokenId` from this contract to the recipient `to` - /// @param tokenId The unique identifier of an Uniswap V3 LP token - /// @param to The address where the LP token will be sent - /// @param data An optional data array that will be passed along to the `to` address via the NFT safeTransferFrom - function withdrawToken( - uint256 tokenId, - address to, - bytes memory data - ) external; - - /// @notice Stakes a Uniswap V3 LP token - /// @param key The key of the incentive for which to stake the NFT - /// @param tokenId The ID of the token to stake - function stakeToken(IncentiveKey memory key, uint256 tokenId) external; - - /// @notice Unstakes a Uniswap V3 LP token - /// @param key The key of the incentive for which to unstake the NFT - /// @param tokenId The ID of the token to unstake - function unstakeToken(IncentiveKey memory key, uint256 tokenId) external; - - /// @notice Transfers `amountRequested` of accrued `rewardToken` rewards from the contract to the recipient `to` - /// @param rewardToken The token being distributed as a reward - /// @param to The address where claimed rewards will be sent to - /// @param amountRequested The amount of reward tokens to claim. Claims entire reward amount if set to 0. - /// @return reward The amount of reward tokens claimed - function claimReward( - IERC20Minimal rewardToken, - address to, - uint256 amountRequested - ) external returns (uint256 reward); - - /// @notice Calculates the reward amount that will be received for the given stake - /// @param key The key of the incentive - /// @param tokenId The ID of the token - /// @return reward The reward accrued to the NFT for the given incentive thus far - function getRewardInfo(IncentiveKey memory key, uint256 tokenId) - external - returns (uint256 reward, uint160 secondsInsideX128); - - /// @notice Event emitted when a liquidity mining incentive has been created - /// @param rewardToken The token being distributed as a reward - /// @param pool The Uniswap V3 pool - /// @param startTime The time when the incentive program begins - /// @param endTime The time when rewards stop accruing - /// @param refundee The address which receives any remaining reward tokens after the end time - /// @param reward The amount of reward tokens to be distributed - event IncentiveCreated( - IERC20Minimal indexed rewardToken, - IUniswapV3Pool indexed pool, - uint256 startTime, - uint256 endTime, - address refundee, - uint256 reward - ); - - /// @notice Event that can be emitted when a liquidity mining incentive has ended - /// @param incentiveId The incentive which is ending - /// @param refund The amount of reward tokens refunded - event IncentiveEnded(bytes32 indexed incentiveId, uint256 refund); - - /// @notice Emitted when ownership of a deposit changes - /// @param tokenId The ID of the deposit (and token) that is being transferred - /// @param oldOwner The owner before the deposit was transferred - /// @param newOwner The owner after the deposit was transferred - event DepositTransferred(uint256 indexed tokenId, address indexed oldOwner, address indexed newOwner); - - /// @notice Event emitted when a Uniswap V3 LP token has been staked - /// @param tokenId The unique identifier of an Uniswap V3 LP token - /// @param liquidity The amount of liquidity staked - /// @param incentiveId The incentive in which the token is staking - event TokenStaked(uint256 indexed tokenId, bytes32 indexed incentiveId, uint128 liquidity); - - /// @notice Event emitted when a Uniswap V3 LP token has been unstaked - /// @param tokenId The unique identifier of an Uniswap V3 LP token - /// @param incentiveId The incentive in which the token is staking - event TokenUnstaked(uint256 indexed tokenId, bytes32 indexed incentiveId); - - /// @notice Event emitted when a reward token has been claimed - /// @param to The address where claimed rewards were sent to - /// @param reward The amount of reward tokens claimed - event RewardClaimed(address indexed to, uint256 reward); -} diff --git a/src/polygon/interfaces/univ3/pool/IUniswapV3PoolActions.sol b/src/polygon/interfaces/univ3/pool/IUniswapV3PoolActions.sol deleted file mode 100644 index 6783a4f9e..000000000 --- a/src/polygon/interfaces/univ3/pool/IUniswapV3PoolActions.sol +++ /dev/null @@ -1,77 +0,0 @@ -// 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 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); -} diff --git a/src/polygon/interfaces/univ3/pool/IUniswapV3PoolDerivedState.sol b/src/polygon/interfaces/univ3/pool/IUniswapV3PoolDerivedState.sol deleted file mode 100644 index a24c28eac..000000000 --- a/src/polygon/interfaces/univ3/pool/IUniswapV3PoolDerivedState.sol +++ /dev/null @@ -1,22 +0,0 @@ -// 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); -} diff --git a/src/polygon/interfaces/univ3/pool/IUniswapV3PoolImmutables.sol b/src/polygon/interfaces/univ3/pool/IUniswapV3PoolImmutables.sol deleted file mode 100644 index 965428235..000000000 --- a/src/polygon/interfaces/univ3/pool/IUniswapV3PoolImmutables.sol +++ /dev/null @@ -1,21 +0,0 @@ -// 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 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 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); -} diff --git a/src/polygon/interfaces/univ3/pool/IUniswapV3PoolState.sol b/src/polygon/interfaces/univ3/pool/IUniswapV3PoolState.sol deleted file mode 100644 index b12fe3ca3..000000000 --- a/src/polygon/interfaces/univ3/pool/IUniswapV3PoolState.sol +++ /dev/null @@ -1,51 +0,0 @@ -// 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 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 - ); -} diff --git a/src/polygon/lib/univ3/FixedPoint96.sol b/src/polygon/lib/univ3/FixedPoint96.sol deleted file mode 100644 index 63b42c294..000000000 --- a/src/polygon/lib/univ3/FixedPoint96.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.4.0; - -/// @title FixedPoint96 -/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) -/// @dev Used in SqrtPriceMath.sol -library FixedPoint96 { - uint8 internal constant RESOLUTION = 96; - uint256 internal constant Q96 = 0x1000000000000000000000000; -} diff --git a/src/polygon/lib/univ3/FullMath.sol b/src/polygon/lib/univ3/FullMath.sol deleted file mode 100644 index 74e7346d7..000000000 --- a/src/polygon/lib/univ3/FullMath.sol +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.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 { - /// @notice Calculates floor(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 - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - function mulDiv( - uint256 a, - uint256 b, - uint256 denominator - ) internal pure returns (uint256 result) { - // 512-bit multiply [prod1 prod0] = a * b - // Compute the product mod 2**256 and mod 2**256 - 1 - // then 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 - uint256 prod0; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(a, b, not(0)) - prod0 := mul(a, b) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - // Handle non-overflow cases, 256 by 256 division - if (prod1 == 0) { - require(denominator > 0); - assembly { - result := div(prod0, denominator) - } - return result; - } - - // Make sure the result is less than 2**256. - // Also prevents denominator == 0 - require(denominator > prod1); - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0] - // Compute remainder using mulmod - uint256 remainder; - assembly { - remainder := mulmod(a, b, denominator) - } - // Subtract 256 bit number from 512 bit number - assembly { - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator - // Compute largest power of two divisor of denominator. - // Always >= 1. - uint256 twos = -denominator & denominator; - // Divide denominator by power of two - assembly { - denominator := div(denominator, twos) - } - - // Divide [prod1 prod0] by the factors of two - assembly { - prod0 := div(prod0, twos) - } - // Shift in bits from prod1 into prod0. For this we need - // to flip `twos` such that it is 2**256 / twos. - // If twos is zero, then it becomes one - assembly { - twos := add(div(sub(0, twos), twos), 1) - } - 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 - // correct for four bits. That is, denominator * inv = 1 mod 2**4 - uint256 inv = (3 * denominator) ^ 2; - // Now use 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. - inv *= 2 - denominator * inv; // inverse mod 2**8 - inv *= 2 - denominator * inv; // inverse mod 2**16 - inv *= 2 - denominator * inv; // inverse mod 2**32 - inv *= 2 - denominator * inv; // inverse mod 2**64 - inv *= 2 - denominator * inv; // inverse mod 2**128 - inv *= 2 - denominator * inv; // 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 precoditions 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 * inv; - 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( - uint256 a, - uint256 b, - uint256 denominator - ) internal pure returns (uint256 result) { - result = mulDiv(a, b, denominator); - if (mulmod(a, b, denominator) > 0) { - require(result < type(uint256).max); - result++; - } - } -} diff --git a/src/polygon/lib/univ3/LiquidityAmounts.sol b/src/polygon/lib/univ3/LiquidityAmounts.sol deleted file mode 100644 index b2ca695fc..000000000 --- a/src/polygon/lib/univ3/LiquidityAmounts.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -import "./FullMath.sol"; -import "./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/polygon/lib/univ3/LowGasSafeMath.sol b/src/polygon/lib/univ3/LowGasSafeMath.sol deleted file mode 100644 index d1c6700c6..000000000 --- a/src/polygon/lib/univ3/LowGasSafeMath.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.12; - -/// @title Optimized overflow and underflow safe math operations -/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost -library LowGasSafeMath { - /// @notice Returns x + y, reverts if sum overflows uint256 - /// @param x The augend - /// @param y The addend - /// @return z The sum of x and y - function add(uint256 x, uint256 y) internal pure returns (uint256 z) { - require((z = x + y) >= x); - } - - /// @notice Returns x - y, reverts if underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { - require((z = x - y) <= x); - } - - /// @notice Returns x * y, reverts if overflows - /// @param x The multiplicand - /// @param y The multiplier - /// @return z The product of x and y - function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { - require(x == 0 || (z = x * y) / x == y); - } - - /// @notice Returns x - y, reverts if underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub( - uint256 x, - uint256 y, - string memory errorMessage - ) internal pure returns (uint256 z) { - require((z = x - y) <= x, errorMessage); - } - - /// @notice Returns x + y, reverts if overflows or underflows - /// @param x The augend - /// @param y The addend - /// @return z The sum of x and y - function add(int256 x, int256 y) internal pure returns (int256 z) { - require((z = x + y) >= x == (y >= 0)); - } - - /// @notice Returns x - y, reverts if overflows or underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub(int256 x, int256 y) internal pure returns (int256 z) { - require((z = x - y) <= x == (y >= 0)); - } - - /// @notice Returns x + y, reverts if sum overflows uint128 - /// @param x The augend - /// @param y The addend - /// @return z The sum of x and y - function add128(uint128 x, uint128 y) internal pure returns (uint128 z) { - require((z = x + y) >= x); - } - - /// @notice Returns x - y, reverts if underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub128(uint128 x, uint128 y) internal pure returns (uint128 z) { - require((z = x - y) <= x); - } - - /// @notice Returns x * y, reverts if overflows - /// @param x The multiplicand - /// @param y The multiplier - /// @return z The product of x and y - function mul128(uint128 x, uint128 y) internal pure returns (uint128 z) { - require(x == 0 || (z = x * y) / x == y); - } - - /// @notice Returns x + y, reverts if sum overflows uint128 - /// @param x The augend - /// @param y The addend - /// @return z The sum of x and y - function add160(uint160 x, uint160 y) internal pure returns (uint160 z) { - require((z = x + y) >= x); - } - - /// @notice Returns x - y, reverts if underflows - /// @param x The minuend - /// @param y The subtrahend - /// @return z The difference of x and y - function sub160(uint160 x, uint160 y) internal pure returns (uint160 z) { - require((z = x - y) <= x); - } - - /// @notice Returns x * y, reverts if overflows - /// @param x The multiplicand - /// @param y The multiplier - /// @return z The product of x and y - function mul160(uint160 x, uint160 y) internal pure returns (uint160 z) { - require(x == 0 || (z = x * y) / x == y); - } -} diff --git a/src/polygon/lib/univ3/PoolActions.sol b/src/polygon/lib/univ3/PoolActions.sol deleted file mode 100644 index 65b457a50..000000000 --- a/src/polygon/lib/univ3/PoolActions.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "../../interfaces/univ3/IUniswapV3Pool.sol"; -import "./PoolVariables.sol"; -import "./SafeCast.sol"; - -/// @title This library is created to conduct a variety of burn liquidity methods -library PoolActions { - using PoolVariables for IUniswapV3Pool; - using LowGasSafeMath for uint256; - using SafeCast for uint256; - - /** - * @notice Withdraws liquidity in share proportion to the Sorbetto's totalSupply. - * @param pool Uniswap V3 pool - * @param tickLower The lower tick of the range - * @param tickUpper The upper tick of the range - * @param totalSupply The amount of total shares in existence - * @param share to burn - * @param to Recipient of amounts - * @return amount0 Amount of token0 withdrawed - * @return amount1 Amount of token1 withdrawed - */ - function burnLiquidityShare( - IUniswapV3Pool pool, - int24 tickLower, - int24 tickUpper, - uint256 totalSupply, - uint256 share, - address to - ) internal returns (uint256 amount0, uint256 amount1) { - require(totalSupply > 0, "TS"); - uint128 liquidityInPool = pool.positionLiquidity(tickLower, tickUpper); - uint256 liquidity = uint256(liquidityInPool).mul(share) / totalSupply; - - if (liquidity > 0) { - (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity.toUint128()); - - if (amount0 > 0 || amount1 > 0) { - // collect liquidity share - (amount0, amount1) = pool.collect(to, tickLower, tickUpper, amount0.toUint128(), amount1.toUint128()); - } - } - } - - /** - * @notice Withdraws exact amount of liquidity - * @param pool Uniswap V3 pool - * @param tickLower The lower tick of the range - * @param tickUpper The upper tick of the range - * @param liquidity to burn - * @param to Recipient of amounts - * @return amount0 Amount of token0 withdrawed - * @return amount1 Amount of token1 withdrawed - */ - function burnExactLiquidity( - IUniswapV3Pool pool, - int24 tickLower, - int24 tickUpper, - uint128 liquidity, - address to - ) internal returns (uint256 amount0, uint256 amount1) { - uint128 liquidityInPool = pool.positionLiquidity(tickLower, tickUpper); - require(liquidityInPool >= liquidity, "TML"); - (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity); - - if (amount0 > 0 || amount1 > 0) { - // collect liquidity share including earned fees - (amount0, amount0) = pool.collect(to, tickLower, tickUpper, amount0.toUint128(), amount1.toUint128()); - } - } - - /** - * @notice Withdraws all liquidity in a range from Uniswap pool - * @param pool Uniswap V3 pool - * @param tickLower The lower tick of the range - * @param tickUpper The upper tick of the range - */ - function burnAllLiquidity( - IUniswapV3Pool pool, - int24 tickLower, - int24 tickUpper - ) internal { - // Burn all liquidity in this range - uint128 liquidity = pool.positionLiquidity(tickLower, tickUpper); - if (liquidity > 0) { - pool.burn(tickLower, tickUpper, liquidity); - } - - // Collect all owed tokens - pool.collect(address(this), tickLower, tickUpper, type(uint128).max, type(uint128).max); - } -} diff --git a/src/polygon/lib/univ3/PoolVariables.sol b/src/polygon/lib/univ3/PoolVariables.sol deleted file mode 100644 index 22cef6a9c..000000000 --- a/src/polygon/lib/univ3/PoolVariables.sol +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -import "./LiquidityAmounts.sol"; -import "../../interfaces/univ3/IUniswapV3Pool.sol"; -import "./TickMath.sol"; -import "./PositionKey.sol"; -import "./LowGasSafeMath.sol"; -import "./SqrtPriceMath.sol"; - -/// @title Liquidity and ticks functions -/// @notice Provides functions for computing liquidity and ticks for token amounts and prices -library PoolVariables { - using LowGasSafeMath for uint256; - - // Cache struct for calculations - struct Info { - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0; - uint256 amount1; - uint128 liquidity; - int24 tickLower; - int24 tickUpper; - } - - /// @dev Wrapper around `LiquidityAmounts.getAmountsForLiquidity()`. - /// @param pool Uniswap V3 pool - /// @param liquidity The liquidity being valued - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return amounts of token0 and token1 that corresponds to liquidity - function amountsForLiquidity( - IUniswapV3Pool pool, - uint128 liquidity, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint256, uint256) { - //Get current price from the pool - (uint160 sqrtRatioX96, , , , , , ) = pool.slot0(); - return - LiquidityAmounts.getAmountsForLiquidity( - sqrtRatioX96, - TickMath.getSqrtRatioAtTick(_tickLower), - TickMath.getSqrtRatioAtTick(_tickUpper), - liquidity - ); - } - - /// @dev Wrapper around `LiquidityAmounts.getLiquidityForAmounts()`. - /// @param pool Uniswap V3 pool - /// @param amount0 The amount of token0 - /// @param amount1 The amount of token1 - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return The maximum amount of liquidity that can be held amount0 and amount1 - function liquidityForAmounts( - IUniswapV3Pool pool, - uint256 amount0, - uint256 amount1, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint128) { - //Get current price from the pool - (uint160 sqrtRatioX96, , , , , , ) = pool.slot0(); - - return - LiquidityAmounts.getLiquidityForAmounts( - sqrtRatioX96, - TickMath.getSqrtRatioAtTick(_tickLower), - TickMath.getSqrtRatioAtTick(_tickUpper), - amount0, - amount1 - ); - } - - /// @dev Amounts of token0 and token1 held in contract position. - /// @param pool Uniswap V3 pool - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return amount0 The amount of token0 held in position - /// @return amount1 The amount of token1 held in position - function positionAmounts( - IUniswapV3Pool pool, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint256 amount0, uint256 amount1) { - //Compute position key - bytes32 positionKey = PositionKey.compute(address(this), _tickLower, _tickUpper); - //Get Position.Info for specified ticks - (uint128 liquidity, , , uint128 tokensOwed0, uint128 tokensOwed1) = pool.positions(positionKey); - // Calc amounts of token0 and token1 including fees - (amount0, amount1) = amountsForLiquidity(pool, liquidity, _tickLower, _tickUpper); - amount0 = amount0.add(uint256(tokensOwed0)); - amount1 = amount1.add(uint256(tokensOwed1)); - } - - /// @dev Amount of liquidity in contract position. - /// @param pool Uniswap V3 pool - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return liquidity stored in position - function positionLiquidity( - IUniswapV3Pool pool, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint128 liquidity) { - //Compute position key - bytes32 positionKey = PositionKey.compute(address(this), _tickLower, _tickUpper); - //Get liquidity stored in position - (liquidity, , , , ) = pool.positions(positionKey); - } - - /// @dev Common checks for valid tick inputs. - /// @param tickLower The lower tick of the range - /// @param tickUpper The upper tick of the range - function checkRange(int24 tickLower, int24 tickUpper) internal pure { - require(tickLower < tickUpper, "TLU"); - require(tickLower >= TickMath.MIN_TICK, "TLM"); - require(tickUpper <= TickMath.MAX_TICK, "TUM"); - } - - /// @dev Rounds tick down towards negative infinity so that it's a multiple - /// of `tickSpacing`. - function floor(int24 tick, int24 tickSpacing) internal pure returns (int24) { - int24 compressed = tick / tickSpacing; - if (tick < 0 && tick % tickSpacing != 0) compressed--; - return compressed * tickSpacing; - } - - /// @dev Gets ticks with proportion equivalent to desired amount - /// @param pool Uniswap V3 pool - /// @param amount0Desired The desired amount of token0 - /// @param amount1Desired The desired amount of token1 - /// @param baseThreshold The range for upper and lower ticks - /// @param tickSpacing The pool tick spacing - /// @return tickLower The lower tick of the range - /// @return tickUpper The upper tick of the range - function getPositionTicks( - IUniswapV3Pool pool, - uint256 amount0Desired, - uint256 amount1Desired, - int24 baseThreshold, - int24 tickSpacing - ) internal view returns (int24 tickLower, int24 tickUpper) { - Info memory cache = Info(amount0Desired, amount1Desired, 0, 0, 0, 0, 0); - // Get current price and tick from the pool - (uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0(); - //Calc base ticks - (cache.tickLower, cache.tickUpper) = baseTicks(currentTick, baseThreshold, tickSpacing); - //Calc amounts of token0 and token1 that can be stored in base range - (cache.amount0, cache.amount1) = amountsForTicks( - pool, - cache.amount0Desired, - cache.amount1Desired, - cache.tickLower, - cache.tickUpper - ); - //Liquidity that can be stored in base range - cache.liquidity = liquidityForAmounts(pool, cache.amount0, cache.amount1, cache.tickLower, cache.tickUpper); - //Get imbalanced token - bool zeroGreaterOne = amountsDirection( - cache.amount0Desired, - cache.amount1Desired, - cache.amount0, - cache.amount1 - ); - //Calc new tick(upper or lower) for imbalanced token - if (zeroGreaterOne) { - uint160 nextSqrtPrice0 = SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp( - sqrtPriceX96, - cache.liquidity, - cache.amount0Desired, - false - ); - cache.tickUpper = PoolVariables.floor(TickMath.getTickAtSqrtRatio(nextSqrtPrice0), tickSpacing); - } else { - uint160 nextSqrtPrice1 = SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown( - sqrtPriceX96, - cache.liquidity, - cache.amount1Desired, - false - ); - cache.tickLower = PoolVariables.floor(TickMath.getTickAtSqrtRatio(nextSqrtPrice1), tickSpacing); - } - checkRange(cache.tickLower, cache.tickUpper); - - tickLower = cache.tickLower; - tickUpper = cache.tickUpper; - } - - /// @dev Gets amounts of token0 and token1 that can be stored in range of upper and lower ticks - /// @param pool Uniswap V3 pool - /// @param amount0Desired The desired amount of token0 - /// @param amount1Desired The desired amount of token1 - /// @param _tickLower The lower tick of the range - /// @param _tickUpper The upper tick of the range - /// @return amount0 amounts of token0 that can be stored in range - /// @return amount1 amounts of token1 that can be stored in range - function amountsForTicks( - IUniswapV3Pool pool, - uint256 amount0Desired, - uint256 amount1Desired, - int24 _tickLower, - int24 _tickUpper - ) internal view returns (uint256 amount0, uint256 amount1) { - uint128 liquidity = liquidityForAmounts(pool, amount0Desired, amount1Desired, _tickLower, _tickUpper); - - (amount0, amount1) = amountsForLiquidity(pool, liquidity, _tickLower, _tickUpper); - } - - /// @dev Calc base ticks depending on base threshold and tickspacing - function baseTicks( - int24 currentTick, - int24 baseThreshold, - int24 tickSpacing - ) internal pure returns (int24 tickLower, int24 tickUpper) { - int24 tickFloor = floor(currentTick, tickSpacing); - - tickLower = tickFloor - baseThreshold; - tickUpper = tickFloor + baseThreshold; - } - - /// @dev Get imbalanced token - /// @param amount0Desired The desired amount of token0 - /// @param amount1Desired The desired amount of token1 - /// @param amount0 Amounts of token0 that can be stored in base range - /// @param amount1 Amounts of token1 that can be stored in base range - /// @return zeroGreaterOne true if token0 is imbalanced. False if token1 is imbalanced - function amountsDirection( - uint256 amount0Desired, - uint256 amount1Desired, - uint256 amount0, - uint256 amount1 - ) internal pure returns (bool zeroGreaterOne) { - zeroGreaterOne = amount0Desired.sub(amount0).mul(amount1Desired) > - amount1Desired.sub(amount1).mul(amount0Desired) - ? true - : false; - } - - // Check price has not moved a lot recently. This mitigates price - // manipulation during rebalance and also prevents placing orders - // when it's too volatile. - function checkDeviation( - IUniswapV3Pool pool, - int24 maxTwapDeviation, - uint32 twapDuration - ) internal view { - (, int24 currentTick, , , , , ) = pool.slot0(); - int24 twap = getTwap(pool, twapDuration); - int24 deviation = currentTick > twap ? currentTick - twap : twap - currentTick; - require(deviation <= maxTwapDeviation, "PSC"); - } - - /// @dev Fetches time-weighted average price in ticks from Uniswap pool for specified duration - function getTwap(IUniswapV3Pool pool, uint32 twapDuration) internal view returns (int24) { - uint32 _twapDuration = twapDuration; - uint32[] memory secondsAgo = new uint32[](2); - secondsAgo[0] = _twapDuration; - secondsAgo[1] = 0; - - (int56[] memory tickCumulatives, ) = pool.observe(secondsAgo); - return int24((tickCumulatives[1] - tickCumulatives[0]) / _twapDuration); - } -} diff --git a/src/polygon/lib/univ3/PositionKey.sol b/src/polygon/lib/univ3/PositionKey.sol deleted file mode 100644 index a60fc18f3..000000000 --- a/src/polygon/lib/univ3/PositionKey.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -library PositionKey { - /// @dev Returns the key of the position in the core library - function compute( - address owner, - int24 tickLower, - int24 tickUpper - ) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(owner, tickLower, tickUpper)); - } -} diff --git a/src/polygon/lib/univ3/SafeCast.sol b/src/polygon/lib/univ3/SafeCast.sol deleted file mode 100644 index 53b70b3cf..000000000 --- a/src/polygon/lib/univ3/SafeCast.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @title Safe casting methods -/// @notice Contains methods for safely casting between types -library SafeCast { - /// @notice Cast a uint256 to a uint160, revert on overflow - /// @param y The uint256 to be downcasted - /// @return z The downcasted integer, now type uint160 - function toUint160(uint256 y) internal pure returns (uint160 z) { - require((z = uint160(y)) == y); - } - - /// @notice Cast a uint256 to a uint128, revert on overflow - /// @param y The uint256 to be downcasted - /// @return z The downcasted integer, now type uint128 - function toUint128(uint256 y) internal pure returns (uint128 z) { - require((z = uint128(y)) == y); - } - - /// @notice Cast a int256 to a int128, revert on overflow or underflow - /// @param y The int256 to be downcasted - /// @return z The downcasted integer, now type int128 - function toInt128(int256 y) internal pure returns (int128 z) { - require((z = int128(y)) == y); - } - - /// @notice Cast a uint256 to a int256, revert on overflow - /// @param y The uint256 to be casted - /// @return z The casted integer, now type int256 - function toInt256(uint256 y) internal pure returns (int256 z) { - require(y < 2**255); - z = int256(y); - } -} diff --git a/src/polygon/lib/univ3/SqrtPriceMath.sol b/src/polygon/lib/univ3/SqrtPriceMath.sol deleted file mode 100644 index d73787e29..000000000 --- a/src/polygon/lib/univ3/SqrtPriceMath.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "./LowGasSafeMath.sol"; -import "./SafeCast.sol"; - -import "./FullMath.sol"; -import "./UnsafeMath.sol"; -import "./FixedPoint96.sol"; - -/// @title Functions based on Q64.96 sqrt price and liquidity -/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas -library SqrtPriceMath { - using LowGasSafeMath for uint256; - using SafeCast for uint256; - - /// @notice Gets the next sqrt price given a delta of token0 - /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least - /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the - /// price less in order to not send too much output. - /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), - /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). - /// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta - /// @param liquidity The amount of usable liquidity - /// @param amount How much of token0 to add or remove from virtual reserves - /// @param add Whether to add or remove the amount of token0 - /// @return The price after adding or removing amount, depending on add - function getNextSqrtPriceFromAmount0RoundingUp( - uint160 sqrtPX96, - uint128 liquidity, - uint256 amount, - bool add - ) internal pure returns (uint160) { - // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price - if (amount == 0) return sqrtPX96; - uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; - - if (add) { - uint256 product; - if ((product = amount * sqrtPX96) / amount == sqrtPX96) { - uint256 denominator = numerator1 + product; - if (denominator >= numerator1) - // always fits in 160 bits - return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); - } - - return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))); - } else { - uint256 product; - // if the product overflows, we know the denominator underflows - // in addition, we must check that the denominator does not underflow - require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); - uint256 denominator = numerator1 - product; - return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); - } - } - - /// @notice Gets the next sqrt price given a delta of token1 - /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least - /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the - /// price less in order to not send too much output. - /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity - /// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta - /// @param liquidity The amount of usable liquidity - /// @param amount How much of token1 to add, or remove, from virtual reserves - /// @param add Whether to add, or remove, the amount of token1 - /// @return The price after adding or removing `amount` - function getNextSqrtPriceFromAmount1RoundingDown( - uint160 sqrtPX96, - uint128 liquidity, - uint256 amount, - bool add - ) internal pure returns (uint160) { - // if we're adding (subtracting), rounding down requires rounding the quotient down (up) - // in both cases, avoid a mulDiv for most inputs - if (add) { - uint256 quotient = ( - amount <= type(uint160).max - ? (amount << FixedPoint96.RESOLUTION) / liquidity - : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) - ); - - return uint256(sqrtPX96).add(quotient).toUint160(); - } else { - uint256 quotient = ( - amount <= type(uint160).max - ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) - : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) - ); - - require(sqrtPX96 > quotient); - // always fits 160 bits - return uint160(sqrtPX96 - quotient); - } - } -} diff --git a/src/polygon/lib/univ3/TickMath.sol b/src/polygon/lib/univ3/TickMath.sol deleted file mode 100644 index 9e09ff459..000000000 --- a/src/polygon/lib/univ3/TickMath.sol +++ /dev/null @@ -1,205 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.6.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) { - uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); - require(absTick <= uint256(MAX_TICK), 'T'); - - uint256 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(uint256).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'); - uint256 ratio = uint256(sqrtPriceX96) << 32; - - uint256 r = ratio; - uint256 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); - - int256 log_2 = (int256(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)) - } - - int256 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/polygon/lib/univ3/UnsafeMath.sol b/src/polygon/lib/univ3/UnsafeMath.sol deleted file mode 100644 index e5944c9d7..000000000 --- a/src/polygon/lib/univ3/UnsafeMath.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.5.0; - -/// @title Math functions that do not check inputs or outputs -/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks -library UnsafeMath { - /// @notice Returns ceil(x / y) - /// @dev division by 0 has unspecified behavior, and must be checked externally - /// @param x The dividend - /// @param y The divisor - /// @return z The quotient, ceil(x / y) - function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { - assembly { - z := add(div(x, y), gt(mod(x, y), 0)) - } - } - - /// @notice Returns floor(x / y) - /// @dev division by 0 has unspecified behavior, and must be checked externally - /// @param x The dividend - /// @param y The divisor - /// @return z The quotient, floor(x / y) - function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 z) { - assembly { - z := div(x, y) - } - } -} diff --git a/src/polygon/pickle-jar-univ3.sol b/src/polygon/pickle-jar-univ3.sol index 185a1f318..111ba91ba 100644 --- a/src/polygon/pickle-jar-univ3.sol +++ b/src/polygon/pickle-jar-univ3.sol @@ -17,12 +17,12 @@ pragma experimental ABIEncoderV2; import "../interfaces/controllerv2.sol"; import "../lib/erc20.sol"; -import "./lib/univ3/PoolActions.sol"; +import "../lib/univ3/PoolActions.sol"; import "../lib/reentrancy-guard.sol"; import "../lib/safe-math.sol"; -import "./interfaces/univ3/IUniswapV3PositionsNFT.sol"; -import "./interfaces/univ3/IUniswapV3Pool.sol"; -import "./interfaces/univ3/ISwapRouter.sol"; +import "../interfaces/univ3/IUniswapV3PositionsNFT.sol"; +import "../interfaces/univ3/IUniswapV3Pool.sol"; +import "../interfaces/univ3/ISwapRouter02.sol"; import "../interfaces/weth.sol"; contract PickleJarUniV3Poly is ERC20, ReentrancyGuard { @@ -274,7 +274,7 @@ contract PickleJarUniV3Poly is ERC20, ReentrancyGuard { ) ); - (_cache.amount0, _cache.amount1) = LiquidityAmounts + (_cache.amount0Accepted, _cache.amount1Accepted) = LiquidityAmounts .getAmountsForLiquidity( sqrtPriceX96, sqrtRatioAX96, @@ -282,23 +282,23 @@ contract PickleJarUniV3Poly is ERC20, ReentrancyGuard { _cache.liquidity ); - if (_cache.amount0Desired > _cache.amount0) + if (_cache.amount0Desired > _cache.amount0Accepted) if ((address(token0) == address(wmatic)) && _maticUsed) - _refundMatic(_cache.amount0Desired.sub(_cache.amount0)); + _refundMatic(_cache.amount0Desired.sub(_cache.amount0Accepted)); else { token0.safeTransfer( msg.sender, - _cache.amount0Desired.sub(_cache.amount0) + _cache.amount0Desired.sub(_cache.amount0Accepted) ); } - if (_cache.amount1Desired > _cache.amount1) + if (_cache.amount1Desired > _cache.amount1Accepted) if ((address(token1) == address(wmatic)) && _maticUsed) - _refundMatic(_cache.amount1Desired.sub(_cache.amount1)); + _refundMatic(_cache.amount1Desired.sub(_cache.amount1Accepted)); else { token1.safeTransfer( msg.sender, - _cache.amount1Desired.sub(_cache.amount1) + _cache.amount1Desired.sub(_cache.amount1Accepted) ); } return _cache.liquidity; @@ -329,7 +329,7 @@ contract PickleJarUniV3Poly is ERC20, ReentrancyGuard { ) ); - (_cache.amount0, _cache.amount1) = LiquidityAmounts + (_cache.amount0Accepted, _cache.amount1Accepted) = LiquidityAmounts .getAmountsForLiquidity( sqrtPriceX96, sqrtRatioAX96, @@ -338,14 +338,14 @@ contract PickleJarUniV3Poly is ERC20, ReentrancyGuard { ); //Determine Trade Direction - bool _zeroForOne = _cache.amount0Desired > _cache.amount0 + bool _zeroForOne = _cache.amount0Desired > _cache.amount0Accepted ? true : false; //Determine Amount to swap uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0)) - : (_cache.amount1Desired.sub(_cache.amount1)); + ? (_cache.amount0Desired.sub(_cache.amount0Accepted)) + : (_cache.amount1Desired.sub(_cache.amount1Accepted)); if (_amountSpecified > 0) { //Determine Token to swap @@ -357,8 +357,8 @@ contract PickleJarUniV3Poly is ERC20, ReentrancyGuard { IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); //Swap the token imbalanced - ISwapRouter(univ3Router).exactInputSingle( - ISwapRouter.ExactInputSingleParams({ + ISwapRouter02(univ3Router).exactInputSingle( + ISwapRouter02.ExactInputSingleParams({ tokenIn: _inputToken, tokenOut: _zeroForOne ? address(token1) : address(token0), fee: pool.fee(), @@ -385,5 +385,6 @@ contract PickleJarUniV3Poly is ERC20, ReentrancyGuard { return this.onERC721Received.selector; } + receive() external payable {} fallback() external payable {} } diff --git a/src/strategies/arbitrum/strategy-sushi-farm-base.sol b/src/strategies/arbitrum/strategy-sushi-farm-base.sol deleted file mode 100644 index cb140d6eb..000000000 --- a/src/strategies/arbitrum/strategy-sushi-farm-base.sol +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "./strategy-base.sol"; -import "../../interfaces/minichefv2.sol"; -import "../../interfaces/IRewarder.sol"; - -abstract contract StrategySushiFarmBase is StrategyBase { - // Token addresses - address public constant sushi = 0xd4d42F0b6DEF4CE0383636770eF773390d85c61A; - address public constant miniChef = - 0xF4d73326C13a4Fc5FD7A064217e12780e9Bd62c3; - - // WETH/ pair - address public token0; - address public token1; - address rewardToken; - - // How much SUSHI tokens to keep? - uint256 public keepSUSHI = 2000; - uint256 public keepReward = 2000; - uint256 public constant keepMax = 10000; - - uint256 public poolId; - - constructor( - address _token0, - address _token1, - uint256 _poolId, - address _lp, - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategyBase(_lp, _governance, _strategist, _controller, _timelock) - { - poolId = _poolId; - token0 = _token0; - token1 = _token1; - } - - function balanceOfPool() public view override returns (uint256) { - (uint256 amount, ) = IMiniChefV2(miniChef).userInfo( - poolId, - address(this) - ); - return amount; - } - - function getHarvestable() external view returns (uint256, uint256) { - uint256 _pendingSushi = IMiniChefV2(miniChef).pendingSushi( - poolId, - address(this) - ); - IRewarder rewarder = IMiniChefV2(miniChef).rewarder(poolId); - (, uint256[] memory _rewardAmounts) = rewarder.pendingTokens( - poolId, - address(this), - 0 - ); - - uint256 _pendingReward; - if (_rewardAmounts.length > 0) { - _pendingReward = _rewardAmounts[0]; - } - // return IMiniChefV2(miniChef).pendingSushi(poolId, address(this)); - return (_pendingSushi, _pendingReward); - } - - // **** Setters **** - - function deposit() public override { - uint256 _want = IERC20(want).balanceOf(address(this)); - if (_want > 0) { - IERC20(want).safeApprove(miniChef, 0); - IERC20(want).safeApprove(miniChef, _want); - IMiniChefV2(miniChef).deposit(poolId, _want, address(this)); - } - } - - function _withdrawSome(uint256 _amount) - internal - override - returns (uint256) - { - IMiniChefV2(miniChef).withdraw(poolId, _amount, address(this)); - return _amount; - } - - // **** Setters **** - - function setKeepSUSHI(uint256 _keepSUSHI) external { - require(msg.sender == timelock, "!timelock"); - keepSUSHI = _keepSUSHI; - } - - function setKeepReward(uint256 _keepReward) external { - require(msg.sender == timelock, "!timelock"); - keepReward = _keepReward; - } - - function setRewardToken(address _rewardToken) external { - require( - msg.sender == timelock || msg.sender == strategist, - "!timelock" - ); - rewardToken = _rewardToken; - } - - // **** State Mutations **** - - function harvest() public override onlyBenevolent { - // Anyone can harvest it at any given time. - // I understand the possibility of being frontrun - // But ETH is a dark forest, and I wanna see how this plays out - // i.e. will be be heavily frontrunned? - // if so, a new strategy will be deployed. - - // Collects SUSHI tokens - IMiniChefV2(miniChef).harvest(poolId, address(this)); - uint256 _sushi = IERC20(sushi).balanceOf(address(this)); - if (_sushi > 0) { - // 10% is locked up for future gov - uint256 _keepSUSHI = _sushi.mul(keepSUSHI).div(keepMax); - IERC20(sushi).safeTransfer( - IController(controller).treasury(), - _keepSUSHI - ); - _swapSushiswap(sushi, weth, _sushi.sub(_keepSUSHI)); - } - - // Collect reward tokens - if (rewardToken != address(0)) { - uint256 _reward = IERC20(rewardToken).balanceOf(address(this)); - if (_reward > 0) { - uint256 _keepReward = _reward.mul(keepReward).div(keepMax); - IERC20(rewardToken).safeTransfer( - IController(controller).treasury(), - _keepReward - ); - _swapSushiswap(rewardToken, weth, _reward.sub(_keepReward)); - } - } - - // Swap half WETH for token0 - uint256 _weth = IERC20(weth).balanceOf(address(this)); - if (_weth > 0 && token0 != weth) { - _swapSushiswap(weth, token0, _weth.div(2)); - } - - // Swap half WETH for token1 - if (_weth > 0 && token1 != weth) { - _swapSushiswap(weth, token1, _weth.div(2)); - } - - // Adds in liquidity for token0/token1 - uint256 _token0 = IERC20(token0).balanceOf(address(this)); - uint256 _token1 = IERC20(token1).balanceOf(address(this)); - if (_token0 > 0 && _token1 > 0) { - IERC20(token0).safeApprove(sushiRouter, 0); - IERC20(token0).safeApprove(sushiRouter, _token0); - IERC20(token1).safeApprove(sushiRouter, 0); - IERC20(token1).safeApprove(sushiRouter, _token1); - - UniswapRouterV2(sushiRouter).addLiquidity( - token0, - token1, - _token0, - _token1, - 0, - 0, - address(this), - now + 60 - ); - - // Donates DUST - IERC20(token0).transfer( - IController(controller).treasury(), - IERC20(token0).balanceOf(address(this)) - ); - IERC20(token1).safeTransfer( - IController(controller).treasury(), - IERC20(token1).balanceOf(address(this)) - ); - } - - // We want to get back SUSHI LP tokens - _distributePerformanceFeesAndDeposit(); - } -} diff --git a/src/strategies/arbitrum/strategy-univ3-rebalance-staker.sol b/src/strategies/arbitrum/strategy-univ3-rebalance-staker.sol deleted file mode 100644 index e6fe33dc3..000000000 --- a/src/strategies/arbitrum/strategy-univ3-rebalance-staker.sol +++ /dev/null @@ -1,690 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "../../lib/erc20.sol"; -import "../../lib/safe-math.sol"; -import "../../lib/univ3/PoolActions.sol"; -import "../../interfaces/uniswapv2.sol"; -import "../../interfaces/univ3/IUniswapV3PositionsNFT.sol"; -import "../../interfaces/univ3/IUniswapV3Pool.sol"; -import "../../interfaces/univ3/IUniswapV3Staker.sol"; -import "../../interfaces/univ3/ISwapRouter.sol"; -import "../../interfaces/controllerv2.sol"; - -abstract contract StrategyRebalanceStakerUniV3 { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - using PoolVariables for IUniswapV3Pool; - - // Perfomance fees - start with 20% - uint256 public performanceTreasuryFee = 2000; - uint256 public constant performanceTreasuryMax = 10000; - - // User accounts - address public governance; - address public controller; - address public strategist; - address public timelock; - - address public univ3_staker; - - // Dex - address public constant univ3Router = - 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; - - // Tokens - IUniswapV3Pool public pool; - - IERC20 public token0; - IERC20 public token1; - uint256 public tokenId; - - int24 public tick_lower; - int24 public tick_upper; - int24 private tickSpacing; - int24 private tickRangeMultiplier; - uint24 private twapTime = 60; - - address public rewardToken; - IUniswapV3PositionsNFT public nftManager = - IUniswapV3PositionsNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); - - mapping(address => bool) public harvesters; - - IUniswapV3Staker.IncentiveKey key; - - event InitialDeposited(uint256 tokenId); - event Harvested(uint256 tokenId); - event Deposited( - uint256 tokenId, - uint256 token0Balance, - uint256 token1Balance - ); - event Withdrawn(uint256 tokenId, uint256 _liquidity); - event Rebalanced(uint256 tokenId, int24 _tickLower, int24 _tickUpper); - - constructor( - address _pool, - int24 _tickRangeMultiplier, - address _governance, - address _strategist, - address _controller, - address _timelock - ) public { - governance = _governance; - strategist = _strategist; - controller = _controller; - timelock = _timelock; - - pool = IUniswapV3Pool(_pool); - - token0 = IERC20(pool.token0()); - token1 = IERC20(pool.token1()); - - tickSpacing = pool.tickSpacing(); - tickRangeMultiplier = _tickRangeMultiplier; - - token0.safeApprove(address(nftManager), uint256(-1)); - token1.safeApprove(address(nftManager), uint256(-1)); - nftManager.setApprovalForAll(univ3_staker, true); - } - - // **** Modifiers **** // - - modifier onlyBenevolent() { - require( - harvesters[msg.sender] || - msg.sender == governance || - msg.sender == strategist - ); - _; - } - - // **** Views **** // - - function liquidityOfThis() public view returns (uint256) { - uint256 liquidity = uint256( - pool.liquidityForAmounts( - token0.balanceOf(address(this)), - token1.balanceOf(address(this)), - tick_lower, - tick_upper - ) - ); - return liquidity; - } - - function liquidityOfPool() public view returns (uint256) { - (, , , , , , , uint128 _liquidity, , , , ) = nftManager.positions( - tokenId - ); - return _liquidity; - } - - function liquidityOf() public view returns (uint256) { - return liquidityOfThis().add(liquidityOfPool()); - } - - function getName() external pure virtual returns (string memory); - - function isStakingActive() public view returns (bool stakingActive) { - return - (block.timestamp >= key.startTime && block.timestamp < key.endTime) - ? true - : false; - } - - // **** Setters **** // - - function whitelistHarvesters(address[] calldata _harvesters) external { - require( - msg.sender == governance || - msg.sender == strategist || - harvesters[msg.sender], - "not authorized" - ); - - for (uint256 i = 0; i < _harvesters.length; i++) { - harvesters[_harvesters[i]] = true; - } - } - - function revokeHarvesters(address[] calldata _harvesters) external { - require( - msg.sender == governance || msg.sender == strategist, - "not authorized" - ); - - for (uint256 i = 0; i < _harvesters.length; i++) { - harvesters[_harvesters[i]] = false; - } - } - - function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) - external - { - require(msg.sender == timelock, "!timelock"); - performanceTreasuryFee = _performanceTreasuryFee; - } - - function setStrategist(address _strategist) external { - require(msg.sender == governance, "!governance"); - strategist = _strategist; - } - - function setGovernance(address _governance) external { - require(msg.sender == governance, "!governance"); - governance = _governance; - } - - function setTimelock(address _timelock) external { - require(msg.sender == timelock, "!timelock"); - timelock = _timelock; - } - - function setController(address _controller) external { - require(msg.sender == timelock, "!timelock"); - controller = _controller; - } - - function setIncentive( - address _rewardToken, - uint256 _startTime, - uint256 _endTime, - address _refundee - ) public onlyBenevolent { - rewardToken = _rewardToken; - key = IUniswapV3Staker.IncentiveKey({ - rewardToken: IERC20Minimal(rewardToken), - pool: IUniswapV3Pool(pool), - startTime: _startTime, - endTime: _endTime, - refundee: _refundee - }); - } - - function setTwapTime(uint24 _twapTime) public { - require(msg.sender == governance, "!governance"); - twapTime = _twapTime; - } - - function setTickRangeMultiplier(int24 _tickRangeMultiplier) public { - require(msg.sender == governance, "!governance"); - tickRangeMultiplier = _tickRangeMultiplier; - } - - function amountsForLiquid() public view returns (uint256, uint256) { - (uint256 a1, uint256 a2) = pool.amountsForLiquidity( - 1e18, - tick_lower, - tick_upper - ); - return (a1, a2); - } - - function determineTicks() public view returns (int24, int24) { - uint32[] memory _observeTime = new uint32[](2); - _observeTime[0] = twapTime; - _observeTime[1] = 0; - (int56[] memory _cumulativeTicks, ) = pool.observe(_observeTime); - int56 _averageTick = (_cumulativeTicks[1] - _cumulativeTicks[0]) / twapTime; - int24 baseThreshold = tickSpacing * tickRangeMultiplier; - return - PoolVariables.baseTicks( - int24(_averageTick), - baseThreshold, - tickSpacing - ); - } - - // **** State mutations **** // - - function deposit() public { - // If NFT is held by staker, then withdraw - if (nftManager.ownerOf(tokenId) != address(this) && isStakingActive()) { - IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); - } - - uint256 _token0 = token0.balanceOf(address(this)); - uint256 _token1 = token1.balanceOf(address(this)); - - if (_token0 > 0 && _token1 > 0) { - nftManager.increaseLiquidity( - IUniswapV3PositionsNFT.IncreaseLiquidityParams({ - tokenId: tokenId, - amount0Desired: _token0, - amount1Desired: _token1, - amount0Min: 0, - amount1Min: 0, - deadline: block.timestamp + 300 - }) - ); - } - redeposit(); - - emit Deposited(tokenId, _token0, _token1); - } - - // Deposit + stake in Uni v3 staker - function redeposit() internal { - if (isStakingActive()) { - nftManager.safeTransferFrom(address(this), univ3_staker, tokenId); - IUniswapV3Staker(univ3_staker).stakeToken(key, tokenId); - } - } - - function _withdrawSome(uint256 _liquidity) - internal - returns (uint256, uint256) - { - if (_liquidity == 0) return (0, 0); - if (isStakingActive()) { - IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); - } - - (uint256 _a0Expect, uint256 _a1Expect) = pool.amountsForLiquidity( - uint128(_liquidity), - tick_lower, - tick_upper - ); - (uint256 amount0, uint256 amount1) = nftManager.decreaseLiquidity( - IUniswapV3PositionsNFT.DecreaseLiquidityParams({ - tokenId: tokenId, - liquidity: uint128(_liquidity), - amount0Min: _a0Expect, - amount1Min: _a1Expect, - deadline: block.timestamp + 300 - }) - ); - - //Only collect decreasedLiquidity, not trading fees. - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: uint128(amount0), - amount1Max: uint128(amount1) - }) - ); - - return (amount0, amount1); - } - - // Controller only function for creating additional rewards from dust - function withdraw(IERC20 _asset) external returns (uint256 balance) { - require(msg.sender == controller, "!controller"); - balance = _asset.balanceOf(address(this)); - _asset.safeTransfer(controller, balance); - } - - // Override base withdraw function to redeposit - function withdraw(uint256 _liquidity) - external - returns (uint256 a0, uint256 a1) - { - require(msg.sender == controller, "!controller"); - (a0, a1) = _withdrawSome(_liquidity); - - address _jar = IControllerV2(controller).jars(address(pool)); - require(_jar != address(0), "!jar"); // additional protection so we don't burn the funds - - token0.safeTransfer(_jar, a0); - token1.safeTransfer(_jar, a1); - - redeposit(); - - emit Withdrawn(tokenId, _liquidity); - } - - // Withdraw all funds, normally used when migrating strategies - function withdrawAll() external returns (uint256 a0, uint256 a1) { - require(msg.sender == controller, "!controller"); - _withdrawAll(); - address _jar = IControllerV2(controller).jars(address(pool)); - require(_jar != address(0), "!jar"); // additional protection so we don't burn the funds - - a0 = token0.balanceOf(address(this)); - a1 = token1.balanceOf(address(this)); - token0.safeTransfer(_jar, a0); - token1.safeTransfer(_jar, a1); - } - - function _withdrawAll() internal returns (uint256 a0, uint256 a1) { - (a0, a1) = _withdrawSome(liquidityOfPool()); - } - - function harvest() public onlyBenevolent { - uint256 _initToken0 = token0.balanceOf(address(this)); - uint256 _initToken1 = token1.balanceOf(address(this)); - - if (isStakingActive()) { - IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - IUniswapV3Staker(univ3_staker).claimReward( - IERC20Minimal(rewardToken), - address(this), - 0 - ); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); - } - - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - - nftManager.sweepToken(address(token0), 0, address(this)); - nftManager.sweepToken(address(token1), 0, address(this)); - - _distributePerformanceFees( - token0.balanceOf(address(this)).sub(_initToken0), - token1.balanceOf(address(this)).sub(_initToken1) - ); - - _balanceProportion(tick_lower, tick_upper); - - deposit(); - - redeposit(); - - emit Harvested(tokenId); - } - - //This assumes rewardToken == token0 - function getHarvestable() public onlyBenevolent returns (uint256, uint256) { - (uint256 _owed0, uint256 _owed1) = nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - - uint256 _stakingRewards; - if (isStakingActive()) { - _stakingRewards = IUniswapV3Staker(univ3_staker).rewards( - key.rewardToken, - address(this) - ); - } - if (address(key.rewardToken) == address(token0)) { - _owed0 = _owed0 + uint128(_stakingRewards); - } else if (address(key.rewardToken) == address(token1)) { - _owed1 = _owed1 + uint128(_stakingRewards); - } - return (uint256(_owed0), uint256(_owed1)); - } - - //Need to call this at end of Liquidity Mining This assumes rewardToken is token0 or token1 - function endOfLM() external onlyBenevolent { - require(block.timestamp > key.endTime, "Not End of LM"); - - uint256 _liqAmt0 = token0.balanceOf(address(this)); - uint256 _liqAmt1 = token1.balanceOf(address(this)); - // claim entire rewards - IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - IUniswapV3Staker(univ3_staker).claimReward( - IERC20Minimal(rewardToken), - address(this), - 0 - ); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); - - _distributePerformanceFees( - token0.balanceOf(address(this)).sub(_liqAmt0), - token1.balanceOf(address(this)).sub(_liqAmt1) - ); - } - - //This assumes rewardToken == (token0 || token1) - function rebalance() - external - onlyBenevolent - returns (uint256 _tokenId) - { - if (tokenId != 0) { - uint256 _initToken0 = token0.balanceOf(address(this)); - uint256 _initToken1 = token1.balanceOf(address(this)); - - if (isStakingActive()) { - // If NFT is held by staker, then withdraw - IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - - // claim entire rewards - IUniswapV3Staker(univ3_staker).claimReward( - IERC20Minimal(rewardToken), - address(this), - 0 - ); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); - } - (, , , , , , , uint256 _liquidity, , , , ) = nftManager.positions( - tokenId - ); - (uint256 _liqAmt0, uint256 _liqAmt1) = nftManager.decreaseLiquidity( - IUniswapV3PositionsNFT.DecreaseLiquidityParams({ - tokenId: tokenId, - liquidity: uint128(_liquidity), - amount0Min: 0, - amount1Min: 0, - deadline: block.timestamp + 300 - }) - ); - - // This has to be done after DecreaseLiquidity to collect the tokens we - // decreased and the fees at the same time. - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - - nftManager.sweepToken(address(token0), 0, address(this)); - nftManager.sweepToken(address(token1), 0, address(this)); - nftManager.burn(tokenId); - - _distributePerformanceFees( - token0.balanceOf(address(this)).sub(_liqAmt0).sub(_initToken0), - token1.balanceOf(address(this)).sub(_liqAmt1).sub(_initToken1) - ); - } - (int24 _tickLower, int24 _tickUpper) = determineTicks(); - _balanceProportion(_tickLower, _tickUpper); - //Need to do this again after the swap to cover any slippage. - uint256 _amount0Desired = token0.balanceOf(address(this)); - uint256 _amount1Desired = token1.balanceOf(address(this)); - - (_tokenId, , , ) = nftManager.mint( - IUniswapV3PositionsNFT.MintParams({ - token0: address(token0), - token1: address(token1), - fee: pool.fee(), - tickLower: _tickLower, - tickUpper: _tickUpper, - amount0Desired: _amount0Desired, - amount1Desired: _amount1Desired, - amount0Min: 0, - amount1Min: 0, - recipient: address(this), - deadline: block.timestamp + 300 - }) - ); - - //Record updated information. - tokenId = _tokenId; - tick_lower = _tickLower; - tick_upper = _tickUpper; - - if (isStakingActive()) { - nftManager.safeTransferFrom(address(this), univ3_staker, tokenId); - IUniswapV3Staker(univ3_staker).stakeToken(key, tokenId); - } - - if (tokenId == 0) { - emit InitialDeposited(_tokenId); - } - - emit Rebalanced(tokenId, _tickLower, _tickUpper); - } - - // **** Emergency functions **** - - function execute(address _target, bytes memory _data) - public - payable - returns (bytes memory response) - { - require(msg.sender == timelock, "!timelock"); - require(_target != address(0), "!target"); - - // call contract in current context - assembly { - let succeeded := delegatecall( - sub(gas(), 5000), - _target, - add(_data, 0x20), - mload(_data), - 0, - 0 - ) - let size := returndatasize() - - response := mload(0x40) - mstore( - 0x40, - add(response, and(add(add(size, 0x20), 0x1f), not(0x1f))) - ) - mstore(response, size) - returndatacopy(add(response, 0x20), 0, size) - - switch iszero(succeeded) - case 1 { - // throw if delegatecall failed - revert(add(response, 0x20), size) - } - } - } - - // **** Internal functions **** - - function _distributePerformanceFees(uint256 _amount0, uint256 _amount1) - internal - { - if (_amount0 > 0) { - IERC20(token0).safeTransfer( - IControllerV2(controller).treasury(), - _amount0.mul(performanceTreasuryFee).div(performanceTreasuryMax) - ); - } - if (_amount1 > 0) { - IERC20(token1).safeTransfer( - IControllerV2(controller).treasury(), - _amount1.mul(performanceTreasuryFee).div(performanceTreasuryMax) - ); - } - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC721Received.selector; - } - - function _balanceProportion(int24 _tickLower, int24 _tickUpper) internal { - PoolVariables.Info memory _cache; - - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - //Get Max Liquidity for Amounts we own. - _cache.liquidity = pool.liquidityForAmounts( - _cache.amount0Desired, - _cache.amount1Desired, - _tickLower, - _tickUpper - ); - - //Get correct amounts of each token for the liquidity we have. - (_cache.amount0, _cache.amount1) = pool.amountsForLiquidity( - _cache.liquidity, - _tickLower, - _tickUpper - ); - - //Determine Trade Direction - bool _zeroForOne; - if (_cache.amount1Desired == 0) { - _zeroForOne = true; - } else { - _zeroForOne = PoolVariables.amountsDirection( - _cache.amount0Desired, - _cache.amount1Desired, - _cache.amount0, - _cache.amount1 - ); - } - - //Determine Amount to swap - uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0).div(2)) - : (_cache.amount1Desired.sub(_cache.amount1).div(2)); - - if (_amountSpecified > 0) { - //Determine Token to swap - address _inputToken = _zeroForOne - ? address(token0) - : address(token1); - - IERC20(_inputToken).safeApprove(univ3Router, 0); - IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); - - //Swap the token imbalanced - ISwapRouter(univ3Router).exactInputSingle( - ISwapRouter.ExactInputSingleParams({ - tokenIn: _inputToken, - tokenOut: _zeroForOne ? address(token1) : address(token0), - fee: pool.fee(), - recipient: address(this), - amountIn: _amountSpecified, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0, - deadline: block.timestamp + 300 - }) - ); - } - } -} diff --git a/src/strategies/arbitrum/strategy-univ3-rebalance.sol b/src/strategies/arbitrum/strategy-univ3-rebalance.sol deleted file mode 100644 index a15d167bb..000000000 --- a/src/strategies/arbitrum/strategy-univ3-rebalance.sol +++ /dev/null @@ -1,526 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.12; -pragma experimental ABIEncoderV2; - -import "../../optimism/lib/erc20.sol"; -import "../../optimism/lib/safe-math.sol"; -import "../../optimism/lib/univ3/PoolActions.sol"; -import "../../optimism/interfaces/uniswapv2.sol"; -import "../../optimism/interfaces/univ3/IUniswapV3PositionsNFT.sol"; -import "../../optimism/interfaces/univ3/IUniswapV3Pool.sol"; -import "../../optimism/interfaces/univ3/ISwapRouter.sol"; -import "../../optimism/interfaces/controllerv2.sol"; - -abstract contract StrategyRebalanceUniV3 { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - using PoolVariables for IUniswapV3Pool; - - // Perfomance fees - start with 20% - uint256 public performanceTreasuryFee = 1000; - uint256 public constant performanceTreasuryMax = 10000; - - address public constant weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; - address public constant native = weth; - - // User accounts - address public governance; - address public controller; - address public strategist; - address public timelock; - - // Dex - address public constant univ3Router = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; - - // Tokens - IUniswapV3Pool public pool; - - IERC20 public token0; - IERC20 public token1; - uint256 public tokenId; - - int24 public tick_lower; - int24 public tick_upper; - int24 private tickSpacing; - int24 private tickRangeMultiplier; - uint24 private twapTime = 60; - - IUniswapV3PositionsNFT public nftManager = IUniswapV3PositionsNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); - - mapping(address => bytes) public tokenToNativeRoutes; - - mapping(address => bool) public harvesters; - - event InitialDeposited(uint256 tokenId); - event Harvested(uint256 tokenId); - event Deposited(uint256 tokenId, uint256 token0Balance, uint256 token1Balance); - event Withdrawn(uint256 tokenId, uint256 _liquidity); - event Rebalanced(uint256 tokenId, int24 _tickLower, int24 _tickUpper); - - constructor( - address _pool, - int24 _tickRangeMultiplier, - address _governance, - address _strategist, - address _controller, - address _timelock - ) public { - governance = _governance; - strategist = _strategist; - controller = _controller; - timelock = _timelock; - - pool = IUniswapV3Pool(_pool); - - token0 = IERC20(pool.token0()); - token1 = IERC20(pool.token1()); - - tickSpacing = pool.tickSpacing(); - tickRangeMultiplier = _tickRangeMultiplier; - - token0.safeApprove(address(nftManager), type(uint256).max); - token1.safeApprove(address(nftManager), type(uint256).max); - } - - // **** Modifiers **** // - - modifier onlyBenevolent() { - require(harvesters[msg.sender] || msg.sender == governance || msg.sender == strategist); - _; - } - - // **** Views **** // - - function liquidityOfThis() public view returns (uint256) { - uint256 liquidity = uint256( - pool.liquidityForAmounts( - token0.balanceOf(address(this)), - token1.balanceOf(address(this)), - tick_lower, - tick_upper - ) - ); - return liquidity; - } - - function liquidityOfPool() public view returns (uint256) { - (, , , , , , , uint128 _liquidity, , , , ) = nftManager.positions(tokenId); - return _liquidity; - } - - function liquidityOf() public view returns (uint256) { - return liquidityOfThis().add(liquidityOfPool()); - } - - function getName() external pure virtual returns (string memory); - - // **** Setters **** // - - function whitelistHarvesters(address[] calldata _harvesters) external { - require(msg.sender == governance || msg.sender == strategist || harvesters[msg.sender], "not authorized"); - - for (uint256 i = 0; i < _harvesters.length; i++) { - harvesters[_harvesters[i]] = true; - } - } - - function revokeHarvesters(address[] calldata _harvesters) external { - require(msg.sender == governance || msg.sender == strategist, "not authorized"); - - for (uint256 i = 0; i < _harvesters.length; i++) { - harvesters[_harvesters[i]] = false; - } - } - - function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) external { - require(msg.sender == timelock, "!timelock"); - performanceTreasuryFee = _performanceTreasuryFee; - } - - function setStrategist(address _strategist) external { - require(msg.sender == governance, "!governance"); - strategist = _strategist; - } - - function setGovernance(address _governance) external { - require(msg.sender == governance, "!governance"); - governance = _governance; - } - - function setTimelock(address _timelock) external { - require(msg.sender == timelock, "!timelock"); - timelock = _timelock; - } - - function setController(address _controller) external { - require(msg.sender == timelock, "!timelock"); - controller = _controller; - } - - function setTwapTime(uint24 _twapTime) public { - require(msg.sender == governance, "!governance"); - twapTime = _twapTime; - } - - function setTickRangeMultiplier(int24 _tickRangeMultiplier) public { - require(msg.sender == governance, "!governance"); - tickRangeMultiplier = _tickRangeMultiplier; - } - - function amountsForLiquid() public view returns (uint256, uint256) { - (uint256 a1, uint256 a2) = pool.amountsForLiquidity(1e18, tick_lower, tick_upper); - return (a1, a2); - } - - function determineTicks() public view returns (int24, int24) { - uint32[] memory _observeTime = new uint32[](2); - _observeTime[0] = twapTime; - _observeTime[1] = 0; - (int56[] memory _cumulativeTicks, ) = pool.observe(_observeTime); - int56 _averageTick = (_cumulativeTicks[1] - _cumulativeTicks[0]) / twapTime; - int24 baseThreshold = tickSpacing * tickRangeMultiplier; - return PoolVariables.baseTicks(int24(_averageTick), baseThreshold, tickSpacing); - } - - // **** State mutations **** // - - function deposit() public { - uint256 _token0 = token0.balanceOf(address(this)); - uint256 _token1 = token1.balanceOf(address(this)); - - if (_token0 > 0 && _token1 > 0) { - nftManager.increaseLiquidity( - IUniswapV3PositionsNFT.IncreaseLiquidityParams({ - tokenId: tokenId, - amount0Desired: _token0, - amount1Desired: _token1, - amount0Min: 0, - amount1Min: 0, - deadline: block.timestamp + 300 - }) - ); - } - - emit Deposited(tokenId, _token0, _token1); - } - - function _withdrawSome(uint256 _liquidity) internal returns (uint256, uint256) { - if (_liquidity == 0) return (0, 0); - - (uint256 _a0Expect, uint256 _a1Expect) = pool.amountsForLiquidity(uint128(_liquidity), tick_lower, tick_upper); - (uint256 amount0, uint256 amount1) = nftManager.decreaseLiquidity( - IUniswapV3PositionsNFT.DecreaseLiquidityParams({ - tokenId: tokenId, - liquidity: uint128(_liquidity), - amount0Min: _a0Expect, - amount1Min: _a1Expect, - deadline: block.timestamp + 300 - }) - ); - - //Only collect decreasedLiquidity, not trading fees. - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: uint128(amount0), - amount1Max: uint128(amount1) - }) - ); - - return (amount0, amount1); - } - - // Controller only function for creating additional rewards from dust - function withdraw(IERC20 _asset) external returns (uint256 balance) { - require(msg.sender == controller, "!controller"); - balance = _asset.balanceOf(address(this)); - _asset.safeTransfer(controller, balance); - } - - function withdraw(uint256 _liquidity) external returns (uint256 a0, uint256 a1) { - require(msg.sender == controller, "!controller"); - (a0, a1) = _withdrawSome(_liquidity); - - address _jar = IControllerV2(controller).jars(address(pool)); - require(_jar != address(0), "!jar"); // additional protection so we don't burn the funds - - token0.safeTransfer(_jar, a0); - token1.safeTransfer(_jar, a1); - - emit Withdrawn(tokenId, _liquidity); - } - - // Withdraw all funds, normally used when migrating strategies - function withdrawAll() external returns (uint256 a0, uint256 a1) { - require(msg.sender == controller, "!controller"); - _withdrawAll(); - address _jar = IControllerV2(controller).jars(address(pool)); - require(_jar != address(0), "!jar"); // additional protection so we don't burn the funds - - a0 = token0.balanceOf(address(this)); - a1 = token1.balanceOf(address(this)); - token0.safeTransfer(_jar, a0); - token1.safeTransfer(_jar, a1); - } - - function _withdrawAll() internal returns (uint256 a0, uint256 a1) { - (a0, a1) = _withdrawSome(liquidityOfPool()); - } - - function harvest() public onlyBenevolent { - uint256 _initToken0 = token0.balanceOf(address(this)); - uint256 _initToken1 = token1.balanceOf(address(this)); - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - - nftManager.sweepToken(address(token0), 0, address(this)); - nftManager.sweepToken(address(token1), 0, address(this)); - - _distributePerformanceFees( - token0.balanceOf(address(this)).sub(_initToken0), - token1.balanceOf(address(this)).sub(_initToken1) - ); - - _balanceProportion(tick_lower, tick_upper); - - deposit(); - - emit Harvested(tokenId); - } - - function getHarvestable() public onlyBenevolent returns (uint256, uint256) { - //This will only update when someone mint/burn/pokes the pool. - (uint256 _owed0, uint256 _owed1) = nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - return (uint256(_owed0), uint256(_owed1)); - } - - function rebalance() external onlyBenevolent returns (uint256 _tokenId) { - if (tokenId != 0) { - uint256 _initToken0 = token0.balanceOf(address(this)); - uint256 _initToken1 = token1.balanceOf(address(this)); - (, , , , , , , uint256 _liquidity, , , , ) = nftManager.positions(tokenId); - (uint256 _liqAmt0, uint256 _liqAmt1) = nftManager.decreaseLiquidity( - IUniswapV3PositionsNFT.DecreaseLiquidityParams({ - tokenId: tokenId, - liquidity: uint128(_liquidity), - amount0Min: 0, - amount1Min: 0, - deadline: block.timestamp + 300 - }) - ); - - // This has to be done after DecreaseLiquidity to collect the tokens we - // decreased and the fees at the same time. - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - - nftManager.sweepToken(address(token0), 0, address(this)); - nftManager.sweepToken(address(token1), 0, address(this)); - nftManager.burn(tokenId); - - _distributePerformanceFees( - token0.balanceOf(address(this)).sub(_liqAmt0).sub(_initToken0), - token1.balanceOf(address(this)).sub(_liqAmt1).sub(_initToken1) - ); - } - - (int24 _tickLower, int24 _tickUpper) = determineTicks(); - _balanceProportion(_tickLower, _tickUpper); - //Need to do this again after the swap to cover any slippage. - uint256 _amount0Desired = token0.balanceOf(address(this)); - uint256 _amount1Desired = token1.balanceOf(address(this)); - - (_tokenId, , , ) = nftManager.mint( - IUniswapV3PositionsNFT.MintParams({ - token0: address(token0), - token1: address(token1), - fee: pool.fee(), - tickLower: _tickLower, - tickUpper: _tickUpper, - amount0Desired: _amount0Desired, - amount1Desired: _amount1Desired, - amount0Min: 0, - amount1Min: 0, - recipient: address(this), - deadline: block.timestamp + 300 - }) - ); - - //Record updated information. - tokenId = _tokenId; - tick_lower = _tickLower; - tick_upper = _tickUpper; - - if (tokenId == 0) { - emit InitialDeposited(_tokenId); - } - - emit Rebalanced(tokenId, _tickLower, _tickUpper); - } - - // **** Emergency functions **** - - function execute(address _target, bytes memory _data) public payable returns (bytes memory response) { - require(msg.sender == timelock, "!timelock"); - require(_target != address(0), "!target"); - - // call contract in current context - assembly { - let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 0) - let size := returndatasize() - - response := mload(0x40) - mstore(0x40, add(response, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - mstore(response, size) - returndatacopy(add(response, 0x20), 0, size) - - switch iszero(succeeded) - case 1 { - // throw if delegatecall failed - revert(add(response, 0x20), size) - } - } - } - - // **** Internal functions **** - function _balanceProportion(int24 _tickLower, int24 _tickUpper) internal { - PoolVariables.Info memory _cache; - - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - //Get Max Liquidity for Amounts we own. - _cache.liquidity = pool.liquidityForAmounts( - _cache.amount0Desired, - _cache.amount1Desired, - _tickLower, - _tickUpper - ); - - //Get correct amounts of each token for the liquidity we have. - (_cache.amount0, _cache.amount1) = pool.amountsForLiquidity(_cache.liquidity, _tickLower, _tickUpper); - - //Determine Trade Direction - bool _zeroForOne; - if (_cache.amount1Desired == 0) { - _zeroForOne = true; - } else { - _zeroForOne = PoolVariables.amountsDirection( - _cache.amount0Desired, - _cache.amount1Desired, - _cache.amount0, - _cache.amount1 - ); - } - - //Determine Amount to swap - uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0).div(2)) - : (_cache.amount1Desired.sub(_cache.amount1).div(2)); - - if (_amountSpecified > 0) { - //Determine Token to swap - address _inputToken = _zeroForOne ? address(token0) : address(token1); - - IERC20(_inputToken).safeApprove(univ3Router, 0); - IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); - - //Swap the token imbalanced - ISwapRouter(univ3Router).exactInputSingle( - ISwapRouter.ExactInputSingleParams({ - tokenIn: _inputToken, - tokenOut: _zeroForOne ? address(token1) : address(token0), - fee: pool.fee(), - recipient: address(this), - amountIn: _amountSpecified, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }) - ); - } - } - - function _swapUniV3WithPath( - address _token, - bytes memory _path, - uint256 _amount - ) internal returns (uint256 _amountOut) { - _amountOut = 0; - if (_path.length > 0) { - IERC20(_token).safeApprove(univ3Router, 0); - IERC20(_token).safeApprove(univ3Router, _amount); - _amountOut = ISwapRouter(univ3Router).exactInput( - ISwapRouter.ExactInputParams({ - path: _path, - recipient: address(this), - amountIn: _amount, - amountOutMinimum: 0 - }) - ); - } - } - - function _distributePerformanceFees(uint256 _amount0, uint256 _amount1) internal { - uint256 _nativeToTreasury; - if (_amount0 > 0) { - uint256 _token0ToTrade = _amount0.mul(performanceTreasuryFee).div(performanceTreasuryMax); - - if (tokenToNativeRoutes[address(token0)].length > 0) { - _nativeToTreasury += _swapUniV3WithPath( - address(token0), - tokenToNativeRoutes[address(token0)], - _token0ToTrade - ); - // token0 is native - } else { - _nativeToTreasury += _token0ToTrade; - } - } - - if (_amount1 > 0) { - uint256 _token1ToTrade = _amount1.mul(performanceTreasuryFee).div(performanceTreasuryMax); - - if (tokenToNativeRoutes[address(token1)].length > 0) { - _nativeToTreasury += _swapUniV3WithPath( - address(token1), - tokenToNativeRoutes[address(token1)], - _token1ToTrade - ); - // token1 is native - } else { - _nativeToTreasury += _token1ToTrade; - } - } - if (_nativeToTreasury > 0) IERC20(native).safeTransfer(IControllerV2(controller).treasury(), _nativeToTreasury); - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC721Received.selector; - } -} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-arb-base.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-arb-base.sol new file mode 100644 index 000000000..efec8e533 --- /dev/null +++ b/src/strategies/arbitrum/sushiswap/strategy-sushi-arb-base.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "../../sushiswap/strategy-sushi-bento-base.sol"; + +abstract contract StrategySushiArbBase is StrategySushiBentoBase { + address private constant _tridentRouter = 0xD9988b4B5bBC53A794240496cfA9Bf5b1F8E0523; + address private constant _sushiRouter = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; + address private constant _bento = 0x74c764D41B77DBbb4fe771daB1939B00b146894A; + address private constant _minichef = 0xF4d73326C13a4Fc5FD7A064217e12780e9Bd62c3; + address private constant _sushi = 0xd4d42F0b6DEF4CE0383636770eF773390d85c61A; + address private constant _native = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + + constructor( + address _want, + uint256 _poolId, + address _governance, + address _strategist, + address _controller, + address _timelock + ) + StrategySushiBentoBase( + _want, + _sushi, + _native, + _bento, + _tridentRouter, + _sushiRouter, + _minichef, + _poolId, + _governance, + _strategist, + _controller, + _timelock + ) + { + performanceTreasuryFee = 1000; + + // Sushi to native route + address[] memory _p = new address[](2); + _p[0] = sushi; + _p[1] = native; + bytes[] memory _encodedFullRouteArr = new bytes[](1); + _encodedFullRouteArr[0] = abi.encode(true, abi.encode(_p)); + _addToNativeRoute(abi.encode(_encodedFullRouteArr)); + } +} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-axlusdc-usdc-sslp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-axlusdc-usdc-sslp.sol new file mode 100644 index 000000000..e91bfdde0 --- /dev/null +++ b/src/strategies/arbitrum/sushiswap/strategy-sushi-axlusdc-usdc-sslp.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-arb-base.sol"; + +contract StrategyArbSushiAxlusdcUsdcSslp is StrategySushiArbBase { + address private constant _lp = 0x863EeD6056918258626b653065588105C54FF2AC; + uint256 private constant _pid = 16; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiArbBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + address[] memory _p0_0 = new address[](2); + _p0_0[0] = native; + _p0_0[1] = token1; + ITridentRouter.Path[] memory _p0_1 = new ITridentRouter.Path[](1); + _p0_1[0] = ITridentRouter.Path({ + pool: 0x863EeD6056918258626b653065588105C54FF2AC, + data: abi.encode(token1, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](2); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0_0)); + _encodedToken0Route[1] = abi.encode(false, abi.encode(_p0_1)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + address[] memory _pr = new address[](2); + _pr[0] = reward; + _pr[1] = native; + bytes[] memory _encodedRewardRoute = new bytes[](1); + _encodedRewardRoute[0] = abi.encode(true, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRewardRoute)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyArbSushiAxlusdcUsdcSslp"; + } +} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-dpx-eth-slp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-dpx-eth-slp.sol new file mode 100644 index 000000000..6458e3a66 --- /dev/null +++ b/src/strategies/arbitrum/sushiswap/strategy-sushi-dpx-eth-slp.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-arb-base.sol"; + +contract StrategyArbSushiDpxEthSlp is StrategySushiArbBase { + address private constant _lp = 0x0C1Cf6883efA1B496B01f654E247B9b419873054; + uint256 private constant _pid = 17; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiArbBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](2); + _p0[0] = native; + _p0[1] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + address[] memory _pr = new address[](2); + _pr[0] = reward; + _pr[1] = native; + bytes[] memory _encodedRewardRoute = new bytes[](1); + _encodedRewardRoute[0] = abi.encode(true, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRewardRoute)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyArbSushiDpxEthSlp"; + } +} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-arb-scplp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-arb-scplp.sol new file mode 100644 index 000000000..e352efbb2 --- /dev/null +++ b/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-arb-scplp.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-arb-base.sol"; + +contract StrategyArbSushiEthArbScplp is StrategySushiArbBase { + address private constant _lp = 0xf3f54a80Cf28d44a0d097c4a67c4c04Eb30da0b5; + uint256 private constant _pid = 27; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiArbBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0xf3f54a80Cf28d44a0d097c4a67c4c04Eb30da0b5, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + ITridentRouter.Path[] memory _pr = new ITridentRouter.Path[](1); + _pr[0] = ITridentRouter.Path({ + pool: 0xf3f54a80Cf28d44a0d097c4a67c4c04Eb30da0b5, + data: abi.encode(reward, address(this), true) + }); + bytes[] memory _encodedRewardRoute = new bytes[](1); + _encodedRewardRoute[0] = abi.encode(false, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRewardRoute)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyArbSushiEthArbScplp"; + } +} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-gohm-slp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-gohm-slp.sol new file mode 100644 index 000000000..f91d401b8 --- /dev/null +++ b/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-gohm-slp.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-arb-base.sol"; + +contract StrategyArbSushiEthGohmSlp is StrategySushiArbBase { + address private constant _lp = 0xaa5bD49f2162ffdC15634c87A77AC67bD51C6a6D; + uint256 private constant _pid = 12; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiArbBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + address[] memory _pr = new address[](2); + _pr[0] = reward; + _pr[1] = native; + bytes[] memory _encodedRewardRoute = new bytes[](1); + _encodedRewardRoute[0] = abi.encode(true, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRewardRoute)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyArbSushiEthGohmSlp"; + } +} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-ohm-lp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-ohm-lp.sol deleted file mode 100644 index 4747e9617..000000000 --- a/src/strategies/arbitrum/sushiswap/strategy-sushi-eth-ohm-lp.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "../strategy-sushi-farm-base.sol"; - -contract StrategySushiEthOhmLp is StrategySushiFarmBase { - // Token/ETH pool id in MasterChef contract - uint256 public sushi_ohm_eth_poolId = 12; - // Token addresses - address public sushi_ohm_eth_lp = 0xaa5bD49f2162ffdC15634c87A77AC67bD51C6a6D; - address public ohm = 0x8D9bA570D6cb60C7e3e0F31343Efe75AB8E65FB1; - - constructor( - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategySushiFarmBase( - weth, - ohm, - sushi_ohm_eth_poolId, - sushi_ohm_eth_lp, - _governance, - _strategist, - _controller, - _timelock - ) - { - rewardToken = ohm; - } - - // **** Views **** - - function getName() external override pure returns (string memory) { - return "StrategySushiEthOhmLp"; - } -} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-lp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-lp.sol deleted file mode 100644 index 11b91bf5a..000000000 --- a/src/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-lp.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "../strategy-sushi-farm-base.sol"; - -contract StrategySushiMagicEthLp is StrategySushiFarmBase { - // Token/ETH pool id in MasterChef contract - uint256 public sushi_magic_eth_poolId = 13; - // Token addresses - address public sushi_magic_eth_lp = 0xB7E50106A5bd3Cf21AF210A755F9C8740890A8c9; - address public magic = 0x539bdE0d7Dbd336b79148AA742883198BBF60342; - - constructor( - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategySushiFarmBase( - magic, - weth, - sushi_magic_eth_poolId, - sushi_magic_eth_lp, - _governance, - _strategist, - _controller, - _timelock - ) - { - rewardToken = magic; - } - - // **** Views **** - - function getName() external override pure returns (string memory) { - return "StrategySushiMagicEthLp"; - } -} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-slp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-slp.sol new file mode 100644 index 000000000..9de1a5ed9 --- /dev/null +++ b/src/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-slp.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-arb-base.sol"; + +contract StrategyArbSushiMagicEthSlp is StrategySushiArbBase { + address private constant _lp = 0xB7E50106A5bd3Cf21AF210A755F9C8740890A8c9; + uint256 private constant _pid = 13; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiArbBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](2); + _p0[0] = native; + _p0[1] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + address[] memory _pr = new address[](2); + _pr[0] = reward; + _pr[1] = native; + bytes[] memory _encodedRewardRoute = new bytes[](1); + _encodedRewardRoute[0] = abi.encode(true, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRewardRoute)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyArbSushiMagicEthSlp"; + } +} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-mim-eth-lp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-mim-eth-lp.sol deleted file mode 100644 index 35156cc03..000000000 --- a/src/strategies/arbitrum/sushiswap/strategy-sushi-mim-eth-lp.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "../strategy-sushi-farm-base.sol"; - -contract StrategySushiEthMimLp is StrategySushiFarmBase { - // Token/ETH pool id in MasterChef contract - uint256 public sushi_mim_poolId = 9; - // Token addresses - address public sushi_eth_mim_lp = 0xb6DD51D5425861C808Fd60827Ab6CFBfFE604959; - address public mim = 0xFEa7a6a0B346362BF88A9e4A88416B77a57D6c2A; - - constructor( - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategySushiFarmBase( - weth, - mim, - sushi_mim_poolId, - sushi_eth_mim_lp, - _governance, - _strategist, - _controller, - _timelock - ) - {} - - // **** Views **** - - function getName() external override pure returns (string memory) { - return "StrategySushiEthMimLp"; - } -} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-rdpx-eth-slp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-rdpx-eth-slp.sol new file mode 100644 index 000000000..74528c8c3 --- /dev/null +++ b/src/strategies/arbitrum/sushiswap/strategy-sushi-rdpx-eth-slp.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-arb-base.sol"; + +contract StrategyArbSushiRdpxEthSlp is StrategySushiArbBase { + address private constant _lp = 0x7418F5A2621E13c05d1EFBd71ec922070794b90a; + uint256 private constant _pid = 23; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiArbBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](2); + _p0[0] = native; + _p0[1] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + address[] memory _pr = new address[](2); + _pr[0] = reward; + _pr[1] = native; + bytes[] memory _encodedRewardRoute = new bytes[](1); + _encodedRewardRoute[0] = abi.encode(true, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRewardRoute)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyArbSushiRdpxEthSlp"; + } +} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-spell-eth-lp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-spell-eth-lp.sol deleted file mode 100644 index 52ae89cc8..000000000 --- a/src/strategies/arbitrum/sushiswap/strategy-sushi-spell-eth-lp.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "../strategy-sushi-farm-base.sol"; - -contract StrategySushiSpellEthLp is StrategySushiFarmBase { - // Token/ETH pool id in MasterChef contract - uint256 public sushi_spell_poolId = 11; - // Token addresses - address public sushi_spell_eth_lp = 0x8f93Eaae544e8f5EB077A1e09C1554067d9e2CA8; - address public spell = 0x3E6648C5a70A150A88bCE65F4aD4d506Fe15d2AF; - - constructor( - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategySushiFarmBase( - weth, - spell, - sushi_spell_poolId, - sushi_spell_eth_lp, - _governance, - _strategist, - _controller, - _timelock - ) - {} - - // **** Views **** - - function getName() external override pure returns (string memory) { - return "StrategySushiSpellEthLp"; - } -} diff --git a/src/strategies/arbitrum/sushiswap/strategy-sushi-usdt-usdc-sslp.sol b/src/strategies/arbitrum/sushiswap/strategy-sushi-usdt-usdc-sslp.sol new file mode 100644 index 000000000..783694123 --- /dev/null +++ b/src/strategies/arbitrum/sushiswap/strategy-sushi-usdt-usdc-sslp.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-arb-base.sol"; + +contract StrategyArbSushiUsdtUsdcSslp is StrategySushiArbBase { + address private constant _lp = 0x79bf7147eBCd0d55e83Cb42ed3Ba1bB2Bb23eF20; + uint256 private constant _pid = 21; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiArbBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + address[] memory _p0 = new address[](2); + _p0[0] = native; + _p0[1] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyArbSushiUsdtUsdcSslp"; + } +} diff --git a/src/strategies/arbitrum/uniswapv3/strategy-univ3-eth-usdc-lp.sol b/src/strategies/arbitrum/uniswapv3/strategy-univ3-eth-usdc-lp.sol index b622f45a6..1fecc397a 100644 --- a/src/strategies/arbitrum/uniswapv3/strategy-univ3-eth-usdc-lp.sol +++ b/src/strategies/arbitrum/uniswapv3/strategy-univ3-eth-usdc-lp.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategyUsdcEthUniV3Arbi is StrategyRebalanceUniV3 { - address public constant usdc_eth_pool = 0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443; - address public constant usdc = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; + address private priv_pool = 0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443; + address private usdc = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; + address private weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; constructor( int24 _tickRangeMultiplier, @@ -16,9 +17,10 @@ contract StrategyUsdcEthUniV3Arbi is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(usdc_eth_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(500), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/arbitrum/uniswapv3/strategy-univ3-gmx-eth-lp.sol b/src/strategies/arbitrum/uniswapv3/strategy-univ3-gmx-eth-lp.sol index 992a35eed..ffb353040 100644 --- a/src/strategies/arbitrum/uniswapv3/strategy-univ3-gmx-eth-lp.sol +++ b/src/strategies/arbitrum/uniswapv3/strategy-univ3-gmx-eth-lp.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategyGmxEthUniV3Arbi is StrategyRebalanceUniV3 { - address public constant gmx_eth_pool = 0x1aEEdD3727A6431b8F070C0aFaA81Cc74f273882; - address public constant gmx = 0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a; + address private priv_pool = 0x1aEEdD3727A6431b8F070C0aFaA81Cc74f273882; + address private gmx = 0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a; + address private weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; constructor( int24 _tickRangeMultiplier, @@ -16,9 +17,10 @@ contract StrategyGmxEthUniV3Arbi is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(gmx_eth_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { tokenToNativeRoutes[gmx] = abi.encodePacked(gmx, uint24(3000), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/arbitrum/uniswapv3/strategy-univ3-staker-eth-usdc-lp.sol b/src/strategies/arbitrum/uniswapv3/strategy-univ3-staker-eth-usdc-lp.sol index ef689bed9..23fa600e1 100644 --- a/src/strategies/arbitrum/uniswapv3/strategy-univ3-staker-eth-usdc-lp.sol +++ b/src/strategies/arbitrum/uniswapv3/strategy-univ3-staker-eth-usdc-lp.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance-staker.sol"; +import "../../uniswapv3/strategy-univ3-rebalance-staker.sol"; contract StrategyUsdcEthUniV3StakerArbi is StrategyRebalanceStakerUniV3 { - address public usdc_eth_pool = 0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443; + address private priv_pool = 0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443; + address private usdc = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; + address private weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; constructor( int24 _tickRangeMultiplier, @@ -15,18 +17,21 @@ contract StrategyUsdcEthUniV3StakerArbi is StrategyRebalanceStakerUniV3 { address _timelock ) public - StrategyRebalanceStakerUniV3(usdc_eth_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceStakerUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { univ3_staker = 0x1f98407aaB862CdDeF78Ed252D6f557aA5b0f00d; rewardToken = 0x0000000000000000000000000000000000000000; key = IUniswapV3Staker.IncentiveKey({ rewardToken: IERC20Minimal(rewardToken), - pool: IUniswapV3Pool(usdc_eth_pool), + pool: IUniswapV3Pool(priv_pool), startTime: 0, endTime: 1, refundee: 0x0000000000000000000000000000000000000000 }); + + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(500), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-btc-eth-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-btc-eth-slp.sol new file mode 100644 index 000000000..1433344d4 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-btc-eth-slp.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiBtcEthSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0x33e29a9eBdD370a8D50656e822aBFD3A910dA1b6; + uint256 private constant _poolId = 5; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](3); + _p0[0] = native; + _p0[1] = token1; // ETH + _p0[2] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiBtcEthSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-crv-eth-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-crv-eth-slp.sol new file mode 100644 index 000000000..0f889fa26 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-crv-eth-slp.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiCrvEthSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0x97A039255ecC352e5782Fc49beA26B03F60FA49b; + uint256 private constant _poolId = 8; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](3); + _p0[0] = native; + _p0[1] = token1; // ETH + _p0[2] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiCrvEthSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-eth-dai-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-eth-dai-slp.sol new file mode 100644 index 000000000..6e08fffbb --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-eth-dai-slp.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiEthDaiSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0x71C8BceEcE3dAf9E27741D2Cc1F03170f862555f; + uint256 private constant _poolId = 3; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](2); + _p0[0] = native; + _p0[1] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + address[] memory _p1 = new address[](3); + _p1[0] = native; + _p1[1] = token0; // ETH + _p1[2] = token1; // DAI + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiEthDaiSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-fantom-base.sol b/src/strategies/fantom/sushiswap/strategy-sushi-fantom-base.sol new file mode 100644 index 000000000..61c0d9061 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-fantom-base.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "../../sushiswap/strategy-sushi-bento-base.sol"; + +abstract contract StrategySushiFantomBase is StrategySushiBentoBase { + address private constant _tridentRouter = address(0); + address private constant _sushiRouter = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; + address private constant _bento = 0xF5BCE5077908a1b7370B9ae04AdC565EBd643966; + address private constant _minichef = 0xf731202A3cf7EfA9368C2d7bD613926f7A144dB5; + address private constant _sushi = 0xae75A438b2E0cB8Bb01Ec1E1e376De11D44477CC; + address private constant _native = 0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83; + + constructor( + address _want, + uint256 _poolId, + address _governance, + address _strategist, + address _controller, + address _timelock + ) + StrategySushiBentoBase( + _want, + _sushi, + _native, + _bento, + _tridentRouter, + _sushiRouter, + _minichef, + _poolId, + _governance, + _strategist, + _controller, + _timelock + ) + { + performanceTreasuryFee = 1000; + + // Sushi to native route + address[] memory _p = new address[](2); + _p[0] = sushi; + _p[1] = native; + bytes[] memory _encodedFullRouteArr = new bytes[](1); + _encodedFullRouteArr[0] = abi.encode(true, abi.encode(_p)); + _addToNativeRoute(abi.encode(_encodedFullRouteArr)); + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-ftm-eth-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-ftm-eth-slp.sol new file mode 100644 index 000000000..51454a678 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-ftm-eth-slp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiFtmEthSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0x3d0BD54c48C2C433ea6fed609Cc3d5Fb7A77622B; + uint256 private constant _poolId = 1; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiFtmEthSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-ftm-link-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-ftm-link-slp.sol new file mode 100644 index 000000000..3784f3091 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-ftm-link-slp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiFtmLinkSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0x1Ca86e57103564F47fFCea7259a6ce8Cc1301549; + uint256 private constant _poolId = 7; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiFtmLinkSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-ftm-sushi-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-ftm-sushi-slp.sol new file mode 100644 index 000000000..73ee1a790 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-ftm-sushi-slp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiFtmSushiSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0x49D2e0DC99C7358D7A9A8633Bf6df111D0EE7F65; + uint256 private constant _poolId = 6; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; // FTM + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiFtmSushiSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-fusdt-ftm-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-fusdt-ftm-slp.sol new file mode 100644 index 000000000..2177e7444 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-fusdt-ftm-slp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiFusdtFtmSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0xd019dd7C760c6431797d6ed170bFFb8FAee11F99; + uint256 private constant _poolId = 2; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](2); + _p0[0] = native; + _p0[1] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiFusdtFtmSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-usdc-ftm-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-usdc-ftm-slp.sol new file mode 100644 index 000000000..5413da5b5 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-usdc-ftm-slp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiUsdcFtmSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0xA48869049e36f8Bfe0Cc5cf655632626988c0140; + uint256 private constant _poolId = 0; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](2); + _p0[0] = native; + _p0[1] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiUsdcFtmSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-usdc-mim-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-usdc-mim-slp.sol new file mode 100644 index 000000000..c864c5232 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-usdc-mim-slp.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiUsdcMimSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0xFFdc0531288dc91C1F49Db03A90Ed84725E9eDa7; + uint256 private constant _poolId = 4; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](2); + _p0[0] = native; + _p0[1] = token0; + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + address[] memory _p1 = new address[](3); + _p1[0] = native; + _p1[1] = token0; // USDC + _p1[2] = token1; // MIM + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiUsdcMimSlp"; + } +} diff --git a/src/strategies/fantom/sushiswap/strategy-sushi-yfi-eth-slp.sol b/src/strategies/fantom/sushiswap/strategy-sushi-yfi-eth-slp.sol new file mode 100644 index 000000000..540c14980 --- /dev/null +++ b/src/strategies/fantom/sushiswap/strategy-sushi-yfi-eth-slp.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-fantom-base.sol"; + +contract StrategyFantomSushiYfiEthSlp is StrategySushiFantomBase { + // Addresses + address private constant _lp = 0xe3eD4237532E6095749a6C984bFCb449C2b86122; + uint256 private constant _poolId = 9; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiFantomBase(_lp, _poolId, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = false; + + // Native to token0 + address[] memory _p0 = new address[](3); + _p0[0] = native; + _p0[1] = token1; // ETH + _p0[2] = token0; // YFI + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(true, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyFantomSushiYfiEthSlp"; + } +} diff --git a/src/strategies/kava/strategy-base.sol b/src/strategies/kava/strategy-base.sol index 2213bea17..54bc07bf8 100644 --- a/src/strategies/kava/strategy-base.sol +++ b/src/strategies/kava/strategy-base.sol @@ -7,7 +7,7 @@ import "../../lib/safe-math.sol"; import "../../interfaces/jar.sol"; import "../../interfaces/masterchef.sol"; import "../../interfaces/trident-router.sol"; -import "../../interfaces/master-bento.sol"; +import "../../interfaces/bentobox.sol"; import "../../interfaces/controller.sol"; // Strategy Contract Basics @@ -63,7 +63,7 @@ abstract contract StrategyBase { require(_strategist != address(0)); require(_controller != address(0)); require(_timelock != address(0)); - IMasterBento(bento).setMasterContractApproval(address(this), tridentRouter, true, 0, "", ""); + IBentoBox(bento).setMasterContractApproval(address(this), tridentRouter, true, 0, "", ""); want = _want; governance = _governance; diff --git a/src/strategies/kava/sushiswap/strategy-sushi-btc-eth-scplp.sol b/src/strategies/kava/sushiswap/strategy-sushi-btc-eth-scplp.sol new file mode 100644 index 000000000..672a959e2 --- /dev/null +++ b/src/strategies/kava/sushiswap/strategy-sushi-btc-eth-scplp.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-kava-base.sol"; + +contract StrategyKavaSushiBtcEthScplp is StrategySushiKavaBase { + address private constant _lp = 0x8070cfc234C4b7f3Ec2AE873885390973f5b15C0; + uint256 private constant _pid = 8; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiKavaBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + ITridentRouter.Path[] memory _p0 = new ITridentRouter.Path[](2); + _p0[0] = ITridentRouter.Path({ + pool: 0xe9c40Af74FCfb85B38Db2a74a7d5d22D731660f5, + data: abi.encode(native, 0x8070cfc234C4b7f3Ec2AE873885390973f5b15C0, false) + }); + _p0[1] = ITridentRouter.Path({ + pool: 0x8070cfc234C4b7f3Ec2AE873885390973f5b15C0, + data: abi.encode(token1, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(false, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0xe9c40Af74FCfb85B38Db2a74a7d5d22D731660f5, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyKavaSushiBtcEthScplp"; + } +} diff --git a/src/strategies/kava/sushiswap/strategy-sushi-kava-axlusdc-scplp.sol b/src/strategies/kava/sushiswap/strategy-sushi-kava-axlusdc-scplp.sol new file mode 100644 index 000000000..8fe9fcd97 --- /dev/null +++ b/src/strategies/kava/sushiswap/strategy-sushi-kava-axlusdc-scplp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-kava-base.sol"; + +contract StrategyKavaSushiKavaAxlusdcScplp is StrategySushiKavaBase { + address private constant _lp = 0xb379Eb428A28a927a16ee7f95100Ac6A5117AaA1; + uint256 private constant _pid = 9; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiKavaBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0xb379Eb428A28a927a16ee7f95100Ac6A5117AaA1, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyKavaSushiKavaAxlusdcScplp"; + } +} diff --git a/src/strategies/kava/sushiswap/strategy-sushi-kava-base.sol b/src/strategies/kava/sushiswap/strategy-sushi-kava-base.sol new file mode 100644 index 000000000..d7d9a29a5 --- /dev/null +++ b/src/strategies/kava/sushiswap/strategy-sushi-kava-base.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "../../sushiswap/strategy-sushi-bento-base.sol"; + +abstract contract StrategySushiKavaBase is StrategySushiBentoBase { + address private constant _tridentRouter = 0xbE811A0D44E2553d25d11CB8DC0d3F0D0E6430E6; + address private constant _sushiRouter = address(0); + address private constant _bento = 0xc35DADB65012eC5796536bD9864eD8773aBc74C4; + address private constant _minichef = 0xf731202A3cf7EfA9368C2d7bD613926f7A144dB5; + address private constant _sushi = 0x7C598c96D02398d89FbCb9d41Eab3DF0C16F227D; + address private constant _native = 0xc86c7C0eFbd6A49B35E8714C5f59D99De09A225b; + + constructor( + address _want, + uint256 _poolId, + address _governance, + address _strategist, + address _controller, + address _timelock + ) + StrategySushiBentoBase( + _want, + _sushi, + _native, + _bento, + _tridentRouter, + _sushiRouter, + _minichef, + _poolId, + _governance, + _strategist, + _controller, + _timelock + ) + { + performanceTreasuryFee = 1000; + + // Sushi to native route + ITridentRouter.Path[] memory _p = new ITridentRouter.Path[](1); + _p[0] = ITridentRouter.Path({ + pool: 0x52089cd962A5665498aEA8D57576e2d3f68eb47D, + data: abi.encode(sushi, address(this), true) + }); + bytes[] memory _encodedFullRouteArr = new bytes[](1); + _encodedFullRouteArr[0] = abi.encode(false, abi.encode(_p)); + _addToNativeRoute(abi.encode(_encodedFullRouteArr)); + } +} diff --git a/src/strategies/kava/sushiswap/strategy-sushi-kava-eth-scplp.sol b/src/strategies/kava/sushiswap/strategy-sushi-kava-eth-scplp.sol new file mode 100644 index 000000000..fc28e9201 --- /dev/null +++ b/src/strategies/kava/sushiswap/strategy-sushi-kava-eth-scplp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-kava-base.sol"; + +contract StrategyKavaSushiKavaEthScplp is StrategySushiKavaBase { + address private constant _lp = 0xe9c40Af74FCfb85B38Db2a74a7d5d22D731660f5; + uint256 private constant _pid = 7; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiKavaBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0xe9c40Af74FCfb85B38Db2a74a7d5d22D731660f5, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyKavaSushiKavaEthScplp"; + } +} diff --git a/src/strategies/kava/sushiswap/strategy-sushi-kava-scplp.sol b/src/strategies/kava/sushiswap/strategy-sushi-kava-scplp.sol new file mode 100644 index 000000000..9e24e766f --- /dev/null +++ b/src/strategies/kava/sushiswap/strategy-sushi-kava-scplp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-kava-base.sol"; + +contract StrategyKavaSushiKavaScplp is StrategySushiKavaBase { + address private constant _lp = 0x52089cd962A5665498aEA8D57576e2d3f68eb47D; + uint256 private constant _pid = 6; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiKavaBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + ITridentRouter.Path[] memory _p0 = new ITridentRouter.Path[](1); + _p0[0] = ITridentRouter.Path({ + pool: 0x52089cd962A5665498aEA8D57576e2d3f68eb47D, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(false, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyKavaSushiKavaScplp"; + } +} diff --git a/src/strategies/kava/sushiswap/strategy-sushi-kava-usdc-scplp.sol b/src/strategies/kava/sushiswap/strategy-sushi-kava-usdc-scplp.sol new file mode 100644 index 000000000..9f1c79c7d --- /dev/null +++ b/src/strategies/kava/sushiswap/strategy-sushi-kava-usdc-scplp.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-kava-base.sol"; + +contract StrategyKavaSushiKavaUsdcScplp is StrategySushiKavaBase { + address private constant _lp = 0x88395b86cF9787E131D2fB5462a22b44056BF574; + uint256 private constant _pid = 0; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiKavaBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0x88395b86cF9787E131D2fB5462a22b44056BF574, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyKavaSushiKavaUsdcScplp"; + } +} diff --git a/src/strategies/optimism/beethovenx/strategy-beetx-ib-reth.sol b/src/strategies/optimism/beethovenx/strategy-beetx-ib-reth.sol new file mode 100644 index 000000000..53049934f --- /dev/null +++ b/src/strategies/optimism/beethovenx/strategy-beetx-ib-reth.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-beetx-base.sol"; + +contract StrategyBeetxIbRethLp is StrategyBeetxBase { + bytes32 private _vaultPoolId = 0x785f08fb77ec934c01736e30546f87b4daccbe50000200000000000000000041; + address private _lp = 0x785F08fB77ec934c01736E30546f87B4daccBe50; + address private _gauge = 0x1C438149E3e210233FCE91eeE1c097d34Fd655c2; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyBeetxBase(_vaultPoolId, _gauge, _lp, _governance, _strategist, _controller, _timelock) { + // Pool IDs + bytes32 ethReth = 0x4fd63966879300cafafbb35d157dc5229278ed2300020000000000000000002b; + bytes32 ethIb = 0xefb0d9f51efd52d7589a9083a6d0ca4de416c24900020000000000000000002c; + + // Tokens addresses + address ib = 0x00a35FD824c717879BF370E70AC6868b95870Dfb; + address reth = 0x9Bcef72be871e61ED4fBbc7630889beE758eb81D; + + // Rewards toNativeRoutes (Need a valid route for every reward token) // + // IB->ETH + // ETH-IB + bytes32[] memory _ibToNativePoolIds = new bytes32[](1); + _ibToNativePoolIds[0] = ethIb; + address[] memory _ibToNativeTokenPath = new address[](2); + _ibToNativeTokenPath[0] = ib; + _ibToNativeTokenPath[1] = native; + _addToNativeRoute(_ibToNativePoolIds, _ibToNativeTokenPath); + + // Pool tokens toTokenRoutes (Only need one token route) // + // ETH->RETH + // ETH-RETH + bytes32[] memory _toRethPoolIds = new bytes32[](1); + _toRethPoolIds[0] = ethReth; + address[] memory _toRethTokenPath = new address[](2); + _toRethTokenPath[0] = native; + _toRethTokenPath[1] = reth; + _addToTokenRoute(_toRethPoolIds, _toRethTokenPath); + } + + function getName() external pure override returns (string memory) { + return "StrategyBeetxIbRethLp"; + } +} diff --git a/src/strategies/optimism/sushiswap/strategy-sushi-eth-op-scplp.sol b/src/strategies/optimism/sushiswap/strategy-sushi-eth-op-scplp.sol new file mode 100644 index 000000000..b6a077316 --- /dev/null +++ b/src/strategies/optimism/sushiswap/strategy-sushi-eth-op-scplp.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-op-base.sol"; + +contract StrategyOpSushiEthOpScplp is StrategySushiOpBase { + address private constant _lp = 0xaA1513Ab4622ED52DeEe4Bd2cD984Fe52F336a63; + uint256 private constant _pid = 0; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiOpBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0xaA1513Ab4622ED52DeEe4Bd2cD984Fe52F336a63, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + ITridentRouter.Path[] memory _pr = new ITridentRouter.Path[](1); + _pr[0] = ITridentRouter.Path({ + pool: 0xaA1513Ab4622ED52DeEe4Bd2cD984Fe52F336a63, + data: abi.encode(reward, address(this), true) + }); + bytes[] memory _encodedRouteArr = new bytes[](1); + _encodedRouteArr[0] = abi.encode(false, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRouteArr)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyOpSushiEthOpScplp"; + } +} diff --git a/src/strategies/optimism/sushiswap/strategy-sushi-eth-usdc-scplp.sol b/src/strategies/optimism/sushiswap/strategy-sushi-eth-usdc-scplp.sol new file mode 100644 index 000000000..b5ebfc1e1 --- /dev/null +++ b/src/strategies/optimism/sushiswap/strategy-sushi-eth-usdc-scplp.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-op-base.sol"; + +contract StrategyOpSushiEthUsdcScplp is StrategySushiOpBase { + address private constant _lp = 0x7086622E6Db990385B102D79CB1218947fb549a9; + uint256 private constant _pid = 3; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiOpBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0x7086622E6Db990385B102D79CB1218947fb549a9, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + ITridentRouter.Path[] memory _pr = new ITridentRouter.Path[](1); + _pr[0] = ITridentRouter.Path({ + pool: 0xaA1513Ab4622ED52DeEe4Bd2cD984Fe52F336a63, + data: abi.encode(reward, address(this), true) + }); + bytes[] memory _encodedRouteArr = new bytes[](1); + _encodedRouteArr[0] = abi.encode(false, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRouteArr)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyOpSushiEthUsdcScplp"; + } +} diff --git a/src/strategies/optimism/sushiswap/strategy-sushi-op-base.sol b/src/strategies/optimism/sushiswap/strategy-sushi-op-base.sol new file mode 100644 index 000000000..a45b77510 --- /dev/null +++ b/src/strategies/optimism/sushiswap/strategy-sushi-op-base.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "../../sushiswap/strategy-sushi-bento-base.sol"; + +abstract contract StrategySushiOpBase is StrategySushiBentoBase { + address private constant _tridentRouter = 0xE52180815c81D7711B83412e53259bed6a3aB70a; + address private constant _sushiRouter = address(0); + address private constant _bento = 0xc35DADB65012eC5796536bD9864eD8773aBc74C4; + address private constant _minichef = 0xB25157bF349295a7Cd31D1751973f426182070D6; + address private constant _sushi = 0x3eaEb77b03dBc0F6321AE1b72b2E9aDb0F60112B; + address private constant _native = 0x4200000000000000000000000000000000000006; + + constructor( + address _want, + uint256 _poolId, + address _governance, + address _strategist, + address _controller, + address _timelock + ) + StrategySushiBentoBase( + _want, + _sushi, + _native, + _bento, + _tridentRouter, + _sushiRouter, + _minichef, + _poolId, + _governance, + _strategist, + _controller, + _timelock + ) + { + performanceTreasuryFee = 1000; + + // Sushi to native route + ITridentRouter.Path[] memory _p = new ITridentRouter.Path[](1); + _p[0] = ITridentRouter.Path({ + pool: 0xdE7629a20245058dd334025ca14Cf121349CAC10, + data: abi.encode(sushi, address(this), true) + }); + bytes[] memory _encodedFullRouteArr = new bytes[](1); + _encodedFullRouteArr[0] = abi.encode(false, abi.encode(_p)); + _addToNativeRoute(abi.encode(_encodedFullRouteArr)); + } +} diff --git a/src/strategies/optimism/sushiswap/strategy-sushi-usdc-susd-sslp.sol b/src/strategies/optimism/sushiswap/strategy-sushi-usdc-susd-sslp.sol new file mode 100644 index 000000000..4f24942e2 --- /dev/null +++ b/src/strategies/optimism/sushiswap/strategy-sushi-usdc-susd-sslp.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-op-base.sol"; + +contract StrategyOpSushiUsdcSusdSslp is StrategySushiOpBase { + address private constant _lp = 0x9Ed1f0Cc0077b5dF99c6aD98B4dccf65f6D8BD04; + uint256 private constant _pid = 2; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiOpBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + ITridentRouter.Path[] memory _p0 = new ITridentRouter.Path[](1); + _p0[0] = ITridentRouter.Path({ + pool: 0x7086622E6Db990385B102D79CB1218947fb549a9, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(false, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](2); + _p1[0] = ITridentRouter.Path({ + pool: 0x7086622E6Db990385B102D79CB1218947fb549a9, + data: abi.encode(native, 0x9Ed1f0Cc0077b5dF99c6aD98B4dccf65f6D8BD04, false) + }); + _p1[1] = ITridentRouter.Path({ + pool: 0x9Ed1f0Cc0077b5dF99c6aD98B4dccf65f6D8BD04, + data: abi.encode(token0, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + ITridentRouter.Path[] memory _pr = new ITridentRouter.Path[](1); + _pr[0] = ITridentRouter.Path({ + pool: 0xaA1513Ab4622ED52DeEe4Bd2cD984Fe52F336a63, + data: abi.encode(reward, address(this), true) + }); + bytes[] memory _encodedRouteArr = new bytes[](1); + _encodedRouteArr[0] = abi.encode(false, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRouteArr)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyOpSushiUsdcSusdSslp"; + } +} diff --git a/src/strategies/optimism/sushiswap/strategy-sushi-usdc-usdt-sslp.sol b/src/strategies/optimism/sushiswap/strategy-sushi-usdc-usdt-sslp.sol new file mode 100644 index 000000000..a61ab8a3a --- /dev/null +++ b/src/strategies/optimism/sushiswap/strategy-sushi-usdc-usdt-sslp.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-op-base.sol"; + +contract StrategyOpSushiUsdcUsdtSslp is StrategySushiOpBase { + address private constant _lp = 0xB059CF6320B29780C39817c42aF1a032bf821D90; + uint256 private constant _pid = 4; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiOpBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + ITridentRouter.Path[] memory _p0 = new ITridentRouter.Path[](1); + _p0[0] = ITridentRouter.Path({ + pool: 0x7086622E6Db990385B102D79CB1218947fb549a9, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(false, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](2); + _p1[0] = ITridentRouter.Path({ + pool: 0x7086622E6Db990385B102D79CB1218947fb549a9, + data: abi.encode(native, 0xB059CF6320B29780C39817c42aF1a032bf821D90, false) + }); + _p1[1] = ITridentRouter.Path({ + pool: 0xB059CF6320B29780C39817c42aF1a032bf821D90, + data: abi.encode(token0, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + ITridentRouter.Path[] memory _pr = new ITridentRouter.Path[](1); + _pr[0] = ITridentRouter.Path({ + pool: 0xaA1513Ab4622ED52DeEe4Bd2cD984Fe52F336a63, + data: abi.encode(reward, address(this), true) + }); + bytes[] memory _encodedRouteArr = new bytes[](1); + _encodedRouteArr[0] = abi.encode(false, abi.encode(_pr)); + _addToNativeRoute(abi.encode(_encodedRouteArr)); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyOpSushiUsdcUsdtSslp"; + } +} diff --git a/src/strategies/optimism/uniswapv3/strategy-univ3-eth-btc-lp.sol b/src/strategies/optimism/uniswapv3/strategy-univ3-eth-btc-lp.sol index 361546f70..5902dcf29 100644 --- a/src/strategies/optimism/uniswapv3/strategy-univ3-eth-btc-lp.sol +++ b/src/strategies/optimism/uniswapv3/strategy-univ3-eth-btc-lp.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategyEthBtcUniV3Optimism is StrategyRebalanceUniV3 { address private priv_pool = 0x73B14a78a0D396C521f954532d43fd5fFe385216; + address private weth = 0x4200000000000000000000000000000000000006; + address private btc = 0x68f180fcCe6836688e9084f035309E29Bf0A2095; constructor( int24 _tickRangeMultiplier, @@ -15,8 +17,9 @@ contract StrategyEthBtcUniV3Optimism is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { + tokenToNativeRoutes[btc] = abi.encodePacked(btc, uint24(3000), weth); performanceTreasuryFee = 2000; } diff --git a/src/strategies/optimism/uniswapv3/strategy-univ3-eth-dai-lp.sol b/src/strategies/optimism/uniswapv3/strategy-univ3-eth-dai-lp.sol index 5bf90dbe3..55e231d9b 100644 --- a/src/strategies/optimism/uniswapv3/strategy-univ3-eth-dai-lp.sol +++ b/src/strategies/optimism/uniswapv3/strategy-univ3-eth-dai-lp.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategyEthDaiUniV3Optimism is StrategyRebalanceUniV3 { address private priv_pool = 0x03aF20bDAaFfB4cC0A521796a223f7D85e2aAc31; + address private weth = 0x4200000000000000000000000000000000000006; + address private dai = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; constructor( int24 _tickRangeMultiplier, @@ -15,8 +17,9 @@ contract StrategyEthDaiUniV3Optimism is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { + tokenToNativeRoutes[dai] = abi.encodePacked(dai, uint24(3000), weth); performanceTreasuryFee = 2000; } diff --git a/src/strategies/optimism/uniswapv3/strategy-univ3-eth-op-lp.sol b/src/strategies/optimism/uniswapv3/strategy-univ3-eth-op-lp.sol index ed52f9135..ea42420bd 100644 --- a/src/strategies/optimism/uniswapv3/strategy-univ3-eth-op-lp.sol +++ b/src/strategies/optimism/uniswapv3/strategy-univ3-eth-op-lp.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategyEthOpUniV3Optimism is StrategyRebalanceUniV3 { address private priv_pool = 0x68F5C0A2DE713a54991E01858Fd27a3832401849; + address private weth = 0x4200000000000000000000000000000000000006; + address private op = 0x4200000000000000000000000000000000000042; constructor( int24 _tickRangeMultiplier, @@ -15,8 +17,9 @@ contract StrategyEthOpUniV3Optimism is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { + tokenToNativeRoutes[op] = abi.encodePacked(op, uint24(3000), weth); performanceTreasuryFee = 2000; } diff --git a/src/strategies/optimism/uniswapv3/strategy-univ3-eth-usdc-lp.sol b/src/strategies/optimism/uniswapv3/strategy-univ3-eth-usdc-lp.sol index 1bb7c14c7..c7a32108f 100644 --- a/src/strategies/optimism/uniswapv3/strategy-univ3-eth-usdc-lp.sol +++ b/src/strategies/optimism/uniswapv3/strategy-univ3-eth-usdc-lp.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategyEthUsdcUniV3Optimism is StrategyRebalanceUniV3 { address private priv_pool = 0x85149247691df622eaF1a8Bd0CaFd40BC45154a9; + address private weth = 0x4200000000000000000000000000000000000006; + address private usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607; constructor( int24 _tickRangeMultiplier, @@ -15,8 +17,9 @@ contract StrategyEthUsdcUniV3Optimism is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(500), weth); performanceTreasuryFee = 2000; } diff --git a/src/strategies/optimism/uniswapv3/strategy-univ3-susd-dai-lp.sol b/src/strategies/optimism/uniswapv3/strategy-univ3-susd-dai-lp.sol index 526cf38c4..bb80218c1 100644 --- a/src/strategies/optimism/uniswapv3/strategy-univ3-susd-dai-lp.sol +++ b/src/strategies/optimism/uniswapv3/strategy-univ3-susd-dai-lp.sol @@ -1,11 +1,15 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategySusdDaiUniV3Optimism is StrategyRebalanceUniV3 { address private priv_pool = 0xAdb35413eC50E0Afe41039eaC8B930d313E94FA4; + address private weth = 0x4200000000000000000000000000000000000006; + address private susd = 0x8c6f28f2F1A3C87F0f938b96d27520d9751ec8d9; + address private dai = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; + address private usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607; constructor( int24 _tickRangeMultiplier, @@ -15,8 +19,10 @@ contract StrategySusdDaiUniV3Optimism is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { + tokenToNativeRoutes[susd] = abi.encodePacked(susd, uint24(500), usdc, uint24(500), weth); + tokenToNativeRoutes[dai] = abi.encodePacked(dai, uint24(3000), weth); performanceTreasuryFee = 1000; } diff --git a/src/strategies/optimism/uniswapv3/strategy-univ3-susd-usdc-lp.sol b/src/strategies/optimism/uniswapv3/strategy-univ3-susd-usdc-lp.sol index ab086676c..2b45444aa 100644 --- a/src/strategies/optimism/uniswapv3/strategy-univ3-susd-usdc-lp.sol +++ b/src/strategies/optimism/uniswapv3/strategy-univ3-susd-usdc-lp.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategySusdUsdcUniV3Optimism is StrategyRebalanceUniV3 { address private priv_pool = 0x8EdA97883a1Bc02Cf68C6B9fb996e06ED8fDb3e5; + address private weth = 0x4200000000000000000000000000000000000006; + address private usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607; + address private susd = 0x8c6f28f2F1A3C87F0f938b96d27520d9751ec8d9; constructor( int24 _tickRangeMultiplier, @@ -15,8 +18,10 @@ contract StrategySusdUsdcUniV3Optimism is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(500), weth); + tokenToNativeRoutes[susd] = abi.encodePacked(susd, uint24(500), usdc, uint24(500), weth); performanceTreasuryFee = 1000; } diff --git a/src/strategies/optimism/uniswapv3/strategy-univ3-usdc-dai-lp.sol b/src/strategies/optimism/uniswapv3/strategy-univ3-usdc-dai-lp.sol index e4d91f6ed..2c916954d 100644 --- a/src/strategies/optimism/uniswapv3/strategy-univ3-usdc-dai-lp.sol +++ b/src/strategies/optimism/uniswapv3/strategy-univ3-usdc-dai-lp.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; contract StrategyUsdcDaiUniV3Optimism is StrategyRebalanceUniV3 { address private priv_pool = 0x100bdC1431A9b09C61c0EFC5776814285f8fB248; + address private weth = 0x4200000000000000000000000000000000000006; + address private usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607; + address private dai = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; constructor( int24 _tickRangeMultiplier, @@ -15,8 +18,10 @@ contract StrategyUsdcDaiUniV3Optimism is StrategyRebalanceUniV3 { address _timelock ) public - StrategyRebalanceUniV3(priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(500), weth); + tokenToNativeRoutes[dai] = abi.encodePacked(dai, uint24(3000), weth); performanceTreasuryFee = 1000; } diff --git a/src/strategies/optimism/velodrome/strategy-velo-eth-reth-vlp.sol b/src/strategies/optimism/velodrome/strategy-velo-eth-reth-vlp.sol new file mode 100644 index 000000000..7f1ad7603 --- /dev/null +++ b/src/strategies/optimism/velodrome/strategy-velo-eth-reth-vlp.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-velo-base.sol"; + +contract StrategyVeloEthRethVlp is StrategyVeloBase { + // Addresses + address private _lp = 0x985612ff2C9409174FedcFf23d4F4761AF124F88; + address private _gauge = 0x89C1a33011Fab92e497963a6FA069aEE5c1f5D44; + address private constant reth = 0x9Bcef72be871e61ED4fBbc7630889beE758eb81D; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyVeloBase( + _lp, + _gauge, + _governance, + _strategist, + _controller, + _timelock + ) + { + isStablePool = false; + + // token1 route + nativeToTokenRoutes[reth].push(ISolidlyRouter.route(native, reth, false)); + } + + // **** Views **** + + function getName() external override pure returns (string memory) { + return "StrategyVeloEthRethVlp"; + } +} diff --git a/src/strategies/optimism/velodrome/strategy-velo-usdc-dai-slp.sol b/src/strategies/optimism/velodrome/strategy-velo-usdc-dai-slp.sol new file mode 100644 index 000000000..5ca831f41 --- /dev/null +++ b/src/strategies/optimism/velodrome/strategy-velo-usdc-dai-slp.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-velo-base.sol"; + +contract StrategyVeloUsdcDaiSlp is StrategyVeloBase { + // Addresses + address private _lp = 0x4F7ebc19844259386DBdDB7b2eB759eeFc6F8353; + address private _gauge = 0xc4fF55A961bC04b880e60219CCBBDD139c6451A4; + address private constant usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607; + address private constant dai = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyVeloBase( + _lp, + _gauge, + _governance, + _strategist, + _controller, + _timelock + ) + { + isStablePool = true; + + // token0 route + nativeToTokenRoutes[usdc].push(ISolidlyRouter.route(native, usdc, false)); + + // token1 route + nativeToTokenRoutes[dai].push(ISolidlyRouter.route(native, usdc, false)); + nativeToTokenRoutes[dai].push(ISolidlyRouter.route(usdc, dai, true)); + } + + // **** Views **** + + function getName() external override pure returns (string memory) { + return "StrategyVeloUsdcDaiSlp"; + } +} diff --git a/src/strategies/optimism/velodrome/strategy-velo-usdc-dola-slp.sol b/src/strategies/optimism/velodrome/strategy-velo-usdc-dola-slp.sol new file mode 100644 index 000000000..c5aa80dd2 --- /dev/null +++ b/src/strategies/optimism/velodrome/strategy-velo-usdc-dola-slp.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-velo-base.sol"; + +contract StrategyVeloUsdcDolaSlp is StrategyVeloBase { + // Addresses + address private _lp = 0x6C5019D345Ec05004A7E7B0623A91a0D9B8D590d; + address private _gauge = 0xAFD2c84b9d1cd50E7E18a55e419749A6c9055E1F; + address private constant usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607; + address private constant dola = 0x8aE125E8653821E851F12A49F7765db9a9ce7384; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyVeloBase( + _lp, + _gauge, + _governance, + _strategist, + _controller, + _timelock + ) + { + isStablePool = true; + + // token0 route + nativeToTokenRoutes[usdc].push(ISolidlyRouter.route(native, usdc, false)); + + // token1 route + nativeToTokenRoutes[dola].push(ISolidlyRouter.route(native, usdc, false)); + nativeToTokenRoutes[dola].push(ISolidlyRouter.route(usdc, dola, true)); + } + + // **** Views **** + + function getName() external override pure returns (string memory) { + return "StrategyVeloUsdcDolaSlp"; + } +} diff --git a/src/strategies/optimism/velodrome/strategy-velo-usdc-tusd-slp.sol b/src/strategies/optimism/velodrome/strategy-velo-usdc-tusd-slp.sol new file mode 100644 index 000000000..e628956e0 --- /dev/null +++ b/src/strategies/optimism/velodrome/strategy-velo-usdc-tusd-slp.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-velo-base.sol"; + +contract StrategyVeloUsdcTusdSlp is StrategyVeloBase { + // Addresses + address private _lp = 0xA4549B89A39f76d9D28415474aeD7d06Ec9935fe; + address private _gauge = 0xc4eAB0D1d7616eA99c15698bb075C2Adb8D2fDc5; + address private constant usdc = 0x7F5c764cBc14f9669B88837ca1490cCa17c31607; + address private constant tusd = 0xcB59a0A753fDB7491d5F3D794316F1adE197B21E; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyVeloBase( + _lp, + _gauge, + _governance, + _strategist, + _controller, + _timelock + ) + { + isStablePool = true; + + // token0 route + nativeToTokenRoutes[usdc].push(ISolidlyRouter.route(native, usdc, false)); + + // token1 route + nativeToTokenRoutes[tusd].push(ISolidlyRouter.route(native, usdc, false)); + nativeToTokenRoutes[tusd].push(ISolidlyRouter.route(usdc, tusd, true)); + } + + // **** Views **** + + function getName() external override pure returns (string memory) { + return "StrategyVeloUsdcTusdSlp"; + } +} diff --git a/src/strategies/optimism/velodrome/strategy-velo-wsteth-eth-vlp.sol b/src/strategies/optimism/velodrome/strategy-velo-wsteth-eth-vlp.sol new file mode 100644 index 000000000..cb233aca6 --- /dev/null +++ b/src/strategies/optimism/velodrome/strategy-velo-wsteth-eth-vlp.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-velo-base.sol"; + +contract StrategyVeloWstethEthVlp is StrategyVeloBase { + // Addresses + address private _lp = 0xc6C1E8399C1c33a3f1959f2f77349D74a373345c; + address private _gauge = 0x150dc0e12d473347BECd0f7352e9dAE6CD30d8aB; + address private constant wsteth = 0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyVeloBase( + _lp, + _gauge, + _governance, + _strategist, + _controller, + _timelock + ) + { + isStablePool = false; + + // token0 route + nativeToTokenRoutes[wsteth].push(ISolidlyRouter.route(native, wsteth, false)); + } + + // **** Views **** + + function getName() external override pure returns (string memory) { + return "StrategyVeloWstethEthVlp"; + } +} diff --git a/src/strategies/optimism/velodrome/strategy-velo-wsteth-seth-slp.sol b/src/strategies/optimism/velodrome/strategy-velo-wsteth-seth-slp.sol new file mode 100644 index 000000000..cb85cb161 --- /dev/null +++ b/src/strategies/optimism/velodrome/strategy-velo-wsteth-seth-slp.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-velo-base.sol"; + +contract StrategyVeloWstethSethSlp is StrategyVeloBase { + // Addresses + address private _lp = 0xB343dae0E7fe28c16EC5dCa64cB0C1ac5F4690AC; + address private _gauge = 0xB30EAC66C790178438667aB220C98Cc983D77383; + address private constant wsteth = 0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb; + address private constant seth = 0xE405de8F52ba7559f9df3C368500B6E6ae6Cee49; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyVeloBase( + _lp, + _gauge, + _governance, + _strategist, + _controller, + _timelock + ) + { + isStablePool = true; + + // token0 route + nativeToTokenRoutes[wsteth].push(ISolidlyRouter.route(native, wsteth, false)); + + // token1 route + nativeToTokenRoutes[seth].push(ISolidlyRouter.route(native, seth, true)); + } + + // **** Views **** + + function getName() external override pure returns (string memory) { + return "StrategyVeloWstethSethSlp"; + } +} diff --git a/src/strategies/polygon/strategy-sushi-farm-base.sol b/src/strategies/polygon/strategy-sushi-farm-base.sol deleted file mode 100644 index dd48b14d0..000000000 --- a/src/strategies/polygon/strategy-sushi-farm-base.sol +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "./strategy-base.sol"; -import "../../interfaces/minichefv2.sol"; -import "../../interfaces/IRewarder.sol"; - -abstract contract StrategySushiFarmBase is StrategyBase { - // Token addresses - address public constant sushi = 0x0b3F868E0BE5597D5DB7fEB59E1CADBb0fdDa50a; - address public constant miniChef = 0x0769fd68dFb93167989C6f7254cd0D766Fb2841F; - - // WETH/ pair - address public token0; - address public token1; - - // How much SUSHI tokens to keep? - uint256 public keepSUSHI = 0; - uint256 public constant keepSUSHIMax = 10000; - - uint256 public poolId; - - constructor( - address _token0, - address _token1, - uint256 _poolId, - address _lp, - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategyBase( - _lp, - _governance, - _strategist, - _controller, - _timelock - ) - { - poolId = _poolId; - token0 = _token0; - token1 = _token1; - } - - function balanceOfPool() public override view returns (uint256) { - (uint256 amount, ) = IMiniChefV2(miniChef).userInfo(poolId, address(this)); - return amount; - } - - function getHarvestable() external view returns (uint256, uint256) { - uint256 _pendingSushi = IMiniChefV2(miniChef).pendingSushi(poolId, address(this)); - IRewarder rewarder = IMiniChefV2(miniChef).rewarder(poolId); - (, uint256[] memory _rewardAmounts) = rewarder.pendingTokens(poolId, address(this), 0); - - uint256 _pendingMatic; - if (_rewardAmounts.length > 0) { - _pendingMatic = _rewardAmounts[0]; - } - // return IMiniChefV2(miniChef).pendingSushi(poolId, address(this)); - return (_pendingSushi, _pendingMatic); - } - - // **** Setters **** - - function deposit() public override { - uint256 _want = IERC20(want).balanceOf(address(this)); - if (_want > 0) { - IERC20(want).safeApprove(miniChef, 0); - IERC20(want).safeApprove(miniChef, _want); - IMiniChefV2(miniChef).deposit(poolId, _want, address(this)); - } - } - - function _withdrawSome(uint256 _amount) - internal - override - returns (uint256) - { - IMiniChefV2(miniChef).withdraw(poolId, _amount, address(this)); - return _amount; - } - - // **** Setters **** - - function setKeepSUSHI(uint256 _keepSUSHI) external { - require(msg.sender == timelock, "!timelock"); - keepSUSHI = _keepSUSHI; - } - - // **** State Mutations **** - - function harvest() public virtual override onlyBenevolent { - // Anyone can harvest it at any given time. - // I understand the possibility of being frontrun - // But ETH is a dark forest, and I wanna see how this plays out - // i.e. will be be heavily frontrunned? - // if so, a new strategy will be deployed. - - // Collects SUSHI tokens - IMiniChefV2(miniChef).harvest(poolId, address(this)); - uint256 _sushi = IERC20(sushi).balanceOf(address(this)); - if (_sushi > 0) { - // 10% is locked up for future gov - uint256 _keepSUSHI = _sushi.mul(keepSUSHI).div(keepSUSHIMax); - IERC20(sushi).safeApprove(IController(controller).treasury(), 0); - IERC20(sushi).safeApprove(IController(controller).treasury(), _keepSUSHI); - IERC20(sushi).safeTransfer( - IController(controller).treasury(), - _keepSUSHI - ); - _swapSushiswap(sushi, weth, _sushi.sub(_keepSUSHI)); - } - - // Collect MATIC tokens - uint256 _wmatic = IERC20(wmatic).balanceOf(address(this)); - if (_wmatic > 0) { - _swapSushiswap(wmatic, weth, _wmatic); - } - - // Swap half WETH for token0 - uint256 _weth = IERC20(weth).balanceOf(address(this)); - if (_weth > 0 && token0 != weth) { - _swapSushiswap(weth, token0, _weth.div(2)); - } - - // Swap half WETH for token1 - if (_weth > 0 && token1 != weth) { - _swapSushiswap(weth, token1, _weth.div(2)); - } - - // Adds in liquidity for token0/token1 - uint256 _token0 = IERC20(token0).balanceOf(address(this)); - uint256 _token1 = IERC20(token1).balanceOf(address(this)); - if (_token0 > 0 && _token1 > 0) { - IERC20(token0).safeApprove(sushiRouter, 0); - IERC20(token0).safeApprove(sushiRouter, _token0); - IERC20(token1).safeApprove(sushiRouter, 0); - IERC20(token1).safeApprove(sushiRouter, _token1); - - UniswapRouterV2(sushiRouter).addLiquidity( - token0, - token1, - _token0, - _token1, - 0, - 0, - address(this), - now + 60 - ); - - // Donates DUST - IERC20(token0).transfer( - IController(controller).treasury(), - IERC20(token0).balanceOf(address(this)) - ); - IERC20(token1).safeTransfer( - IController(controller).treasury(), - IERC20(token1).balanceOf(address(this)) - ); - } - - // We want to get back SUSHI LP tokens - _distributePerformanceFeesAndDeposit(); - } -} diff --git a/src/strategies/polygon/strategy-univ3-rebalance.sol b/src/strategies/polygon/strategy-univ3-rebalance.sol deleted file mode 100644 index 7704c0860..000000000 --- a/src/strategies/polygon/strategy-univ3-rebalance.sol +++ /dev/null @@ -1,551 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "../../lib/erc20.sol"; -import "../../lib/safe-math.sol"; -import "../../polygon/lib/univ3/PoolActions.sol"; -import "../../interfaces/uniswapv2.sol"; -import "../../polygon/interfaces/univ3/IUniswapV3PositionsNFT.sol"; -import "../../polygon/interfaces/univ3/IUniswapV3Pool.sol"; -import "../../polygon/interfaces/univ3/ISwapRouter.sol"; -import "../../interfaces/controllerv2.sol"; - -abstract contract StrategyRebalanceUniV3 { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - using PoolVariables for IUniswapV3Pool; - - // Perfomance fees - start with 20% - uint256 public performanceTreasuryFee = 2000; - uint256 public constant performanceTreasuryMax = 10000; - - // User accounts - address public governance; - address public controller; - address public strategist; - address public timelock; - - // Dex - address public constant univ3Router = - 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; - - // Tokens - IUniswapV3Pool public pool; - - IERC20 public token0; - IERC20 public token1; - uint256 public tokenId; - - int24 public tick_lower; - int24 public tick_upper; - int24 private tickSpacing; - int24 private tickRangeMultiplier; - uint24 private twapTime = 60; - - IUniswapV3PositionsNFT public nftManager = - IUniswapV3PositionsNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); - - mapping(address => bool) public harvesters; - - event InitialDeposited(uint256 tokenId); - event Harvested(uint256 tokenId); - event Deposited( - uint256 tokenId, - uint256 token0Balance, - uint256 token1Balance - ); - event Withdrawn(uint256 tokenId, uint256 _liquidity); - event Rebalanced(uint256 tokenId, int24 _tickLower, int24 _tickUpper); - - constructor( - address _pool, - int24 _tickRangeMultiplier, - address _governance, - address _strategist, - address _controller, - address _timelock - ) public { - governance = _governance; - strategist = _strategist; - controller = _controller; - timelock = _timelock; - - pool = IUniswapV3Pool(_pool); - - token0 = IERC20(pool.token0()); - token1 = IERC20(pool.token1()); - - tickSpacing = pool.tickSpacing(); - tickRangeMultiplier = _tickRangeMultiplier; - - token0.safeApprove(address(nftManager), uint256(-1)); - token1.safeApprove(address(nftManager), uint256(-1)); - } - - // **** Modifiers **** // - - modifier onlyBenevolent() { - require( - harvesters[msg.sender] || - msg.sender == governance || - msg.sender == strategist - ); - _; - } - - // **** Views **** // - - function liquidityOfThis() public view returns (uint256) { - uint256 liquidity = uint256( - pool.liquidityForAmounts( - token0.balanceOf(address(this)), - token1.balanceOf(address(this)), - tick_lower, - tick_upper - ) - ); - return liquidity; - } - - function liquidityOfPool() public view returns (uint256) { - (, , , , , , , uint128 _liquidity, , , , ) = nftManager.positions( - tokenId - ); - return _liquidity; - } - - function liquidityOf() public view returns (uint256) { - return liquidityOfThis().add(liquidityOfPool()); - } - - function getName() external pure virtual returns (string memory); - - // **** Setters **** // - - function whitelistHarvesters(address[] calldata _harvesters) external { - require( - msg.sender == governance || - msg.sender == strategist || - harvesters[msg.sender], - "not authorized" - ); - - for (uint256 i = 0; i < _harvesters.length; i++) { - harvesters[_harvesters[i]] = true; - } - } - - function revokeHarvesters(address[] calldata _harvesters) external { - require( - msg.sender == governance || msg.sender == strategist, - "not authorized" - ); - - for (uint256 i = 0; i < _harvesters.length; i++) { - harvesters[_harvesters[i]] = false; - } - } - - function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) - external - { - require(msg.sender == timelock, "!timelock"); - performanceTreasuryFee = _performanceTreasuryFee; - } - - function setStrategist(address _strategist) external { - require(msg.sender == governance, "!governance"); - strategist = _strategist; - } - - function setGovernance(address _governance) external { - require(msg.sender == governance, "!governance"); - governance = _governance; - } - - function setTimelock(address _timelock) external { - require(msg.sender == timelock, "!timelock"); - timelock = _timelock; - } - - function setController(address _controller) external { - require(msg.sender == timelock, "!timelock"); - controller = _controller; - } - - function setTwapTime(uint24 _twapTime) public { - require(msg.sender == governance, "!governance"); - twapTime = _twapTime; - } - - function setTickRangeMultiplier(int24 _tickRangeMultiplier) public { - require(msg.sender == governance, "!governance"); - tickRangeMultiplier = _tickRangeMultiplier; - } - - function amountsForLiquid() public view returns (uint256, uint256) { - (uint256 a1, uint256 a2) = pool.amountsForLiquidity( - 1e18, - tick_lower, - tick_upper - ); - return (a1, a2); - } - - function determineTicks() public view returns (int24, int24) { - uint32[] memory _observeTime = new uint32[](2); - _observeTime[0] = twapTime; - _observeTime[1] = 0; - (int56[] memory _cumulativeTicks, ) = pool.observe(_observeTime); - int56 _averageTick = (_cumulativeTicks[1] - _cumulativeTicks[0]) / twapTime; - int24 baseThreshold = tickSpacing * tickRangeMultiplier; - return - PoolVariables.baseTicks( - int24(_averageTick), - baseThreshold, - tickSpacing - ); - } - - // **** State mutations **** // - - function deposit() public { - uint256 _token0 = token0.balanceOf(address(this)); - uint256 _token1 = token1.balanceOf(address(this)); - - if (_token0 > 0 && _token1 > 0) { - nftManager.increaseLiquidity( - IUniswapV3PositionsNFT.IncreaseLiquidityParams({ - tokenId: tokenId, - amount0Desired: _token0, - amount1Desired: _token1, - amount0Min: 0, - amount1Min: 0, - deadline: block.timestamp + 300 - }) - ); - } - - emit Deposited(tokenId, _token0, _token1); - } - - function _withdrawSome(uint256 _liquidity) - internal - returns (uint256, uint256) - { - if (_liquidity == 0) return (0, 0); - - (uint256 _a0Expect, uint256 _a1Expect) = pool.amountsForLiquidity( - uint128(_liquidity), - tick_lower, - tick_upper - ); - (uint256 amount0, uint256 amount1) = nftManager.decreaseLiquidity( - IUniswapV3PositionsNFT.DecreaseLiquidityParams({ - tokenId: tokenId, - liquidity: uint128(_liquidity), - amount0Min: _a0Expect, - amount1Min: _a1Expect, - deadline: block.timestamp + 300 - }) - ); - - //Only collect decreasedLiquidity, not trading fees. - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: uint128(amount0), - amount1Max: uint128(amount1) - }) - ); - - return (amount0, amount1); - } - - // Controller only function for creating additional rewards from dust - function withdraw(IERC20 _asset) external returns (uint256 balance) { - require(msg.sender == controller, "!controller"); - balance = _asset.balanceOf(address(this)); - _asset.safeTransfer(controller, balance); - } - - function withdraw(uint256 _liquidity) - external - returns (uint256 a0, uint256 a1) - { - require(msg.sender == controller, "!controller"); - (a0, a1) = _withdrawSome(_liquidity); - - address _jar = IControllerV2(controller).jars(address(pool)); - require(_jar != address(0), "!jar"); // additional protection so we don't burn the funds - - token0.safeTransfer(_jar, a0); - token1.safeTransfer(_jar, a1); - - emit Withdrawn(tokenId, _liquidity); - } - - // Withdraw all funds, normally used when migrating strategies - function withdrawAll() external returns (uint256 a0, uint256 a1) { - require(msg.sender == controller, "!controller"); - _withdrawAll(); - address _jar = IControllerV2(controller).jars(address(pool)); - require(_jar != address(0), "!jar"); // additional protection so we don't burn the funds - - a0 = token0.balanceOf(address(this)); - a1 = token1.balanceOf(address(this)); - token0.safeTransfer(_jar, a0); - token1.safeTransfer(_jar, a1); - } - - function _withdrawAll() internal returns (uint256 a0, uint256 a1) { - (a0, a1) = _withdrawSome(liquidityOfPool()); - } - - function harvest() public onlyBenevolent { - uint256 _initToken0 = token0.balanceOf(address(this)); - uint256 _initToken1 = token1.balanceOf(address(this)); - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - - nftManager.sweepToken(address(token0), 0, address(this)); - nftManager.sweepToken(address(token1), 0, address(this)); - - _distributePerformanceFees( - token0.balanceOf(address(this)).sub(_initToken0), - token1.balanceOf(address(this)).sub(_initToken1) - ); - - _balanceProportion(tick_lower, tick_upper); - - deposit(); - - emit Harvested(tokenId); - } - - function getHarvestable() public onlyBenevolent returns (uint256, uint256) { - //This will only update when someone mint/burn/pokes the pool. - (uint256 _owed0, uint256 _owed1) = nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - return (uint256(_owed0), uint256(_owed1)); - } - - function rebalance() - external - onlyBenevolent - returns (uint256 _tokenId) - { - if (tokenId != 0) { - uint256 _initToken0 = token0.balanceOf(address(this)); - uint256 _initToken1 = token1.balanceOf(address(this)); - (, , , , , , , uint256 _liquidity, , , , ) = nftManager.positions( - tokenId - ); - (uint256 _liqAmt0, uint256 _liqAmt1) = nftManager.decreaseLiquidity( - IUniswapV3PositionsNFT.DecreaseLiquidityParams({ - tokenId: tokenId, - liquidity: uint128(_liquidity), - amount0Min: 0, - amount1Min: 0, - deadline: block.timestamp + 300 - }) - ); - - // This has to be done after DecreaseLiquidity to collect the tokens we - // decreased and the fees at the same time. - nftManager.collect( - IUniswapV3PositionsNFT.CollectParams({ - tokenId: tokenId, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }) - ); - - nftManager.sweepToken(address(token0), 0, address(this)); - nftManager.sweepToken(address(token1), 0, address(this)); - nftManager.burn(tokenId); - - _distributePerformanceFees( - token0.balanceOf(address(this)).sub(_liqAmt0).sub(_initToken0), - token1.balanceOf(address(this)).sub(_liqAmt1).sub(_initToken1) - ); - } - - (int24 _tickLower, int24 _tickUpper) = determineTicks(); - _balanceProportion(_tickLower, _tickUpper); - //Need to do this again after the swap to cover any slippage. - uint256 _amount0Desired = token0.balanceOf(address(this)); - uint256 _amount1Desired = token1.balanceOf(address(this)); - - (_tokenId, , , ) = nftManager.mint( - IUniswapV3PositionsNFT.MintParams({ - token0: address(token0), - token1: address(token1), - fee: pool.fee(), - tickLower: _tickLower, - tickUpper: _tickUpper, - amount0Desired: _amount0Desired, - amount1Desired: _amount1Desired, - amount0Min: 0, - amount1Min: 0, - recipient: address(this), - deadline: block.timestamp + 300 - }) - ); - - //Record updated information. - tokenId = _tokenId; - tick_lower = _tickLower; - tick_upper = _tickUpper; - - if (tokenId == 0) { - emit InitialDeposited(_tokenId); - } - - emit Rebalanced(tokenId, _tickLower, _tickUpper); - } - - // **** Emergency functions **** - - function execute(address _target, bytes memory _data) - public - payable - returns (bytes memory response) - { - require(msg.sender == timelock, "!timelock"); - require(_target != address(0), "!target"); - - // call contract in current context - assembly { - let succeeded := delegatecall( - sub(gas(), 5000), - _target, - add(_data, 0x20), - mload(_data), - 0, - 0 - ) - let size := returndatasize() - - response := mload(0x40) - mstore( - 0x40, - add(response, and(add(add(size, 0x20), 0x1f), not(0x1f))) - ) - mstore(response, size) - returndatacopy(add(response, 0x20), 0, size) - - switch iszero(succeeded) - case 1 { - // throw if delegatecall failed - revert(add(response, 0x20), size) - } - } - } - - // **** Internal functions **** - function _balanceProportion(int24 _tickLower, int24 _tickUpper) internal { - PoolVariables.Info memory _cache; - - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - //Get Max Liquidity for Amounts we own. - _cache.liquidity = pool.liquidityForAmounts( - _cache.amount0Desired, - _cache.amount1Desired, - _tickLower, - _tickUpper - ); - - //Get correct amounts of each token for the liquidity we have. - (_cache.amount0, _cache.amount1) = pool.amountsForLiquidity( - _cache.liquidity, - _tickLower, - _tickUpper - ); - - //Determine Trade Direction - bool _zeroForOne; - if (_cache.amount1Desired == 0) { - _zeroForOne = true; - } else { - _zeroForOne = PoolVariables.amountsDirection( - _cache.amount0Desired, - _cache.amount1Desired, - _cache.amount0, - _cache.amount1 - ); - } - - //Determine Amount to swap - uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0).div(2)) - : (_cache.amount1Desired.sub(_cache.amount1).div(2)); - - if (_amountSpecified > 0) { - //Determine Token to swap - address _inputToken = _zeroForOne - ? address(token0) - : address(token1); - - IERC20(_inputToken).safeApprove(univ3Router, 0); - IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); - - //Swap the token imbalanced - ISwapRouter(univ3Router).exactInputSingle( - ISwapRouter.ExactInputSingleParams({ - tokenIn: _inputToken, - tokenOut: _zeroForOne ? address(token1) : address(token0), - fee: pool.fee(), - recipient: address(this), - amountIn: _amountSpecified, - amountOutMinimum: 0, - sqrtPriceLimitX96: 0 - }) - ); - } - } - - function _distributePerformanceFees(uint256 _amount0, uint256 _amount1) - internal - { - if (_amount0 > 0) { - IERC20(token0).safeTransfer( - IControllerV2(controller).treasury(), - _amount0.mul(performanceTreasuryFee).div(performanceTreasuryMax) - ); - } - if (_amount1 > 0) { - IERC20(token1).safeTransfer( - IControllerV2(controller).treasury(), - _amount1.mul(performanceTreasuryFee).div(performanceTreasuryMax) - ); - } - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC721Received.selector; - } -} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-eth-usdt-lp.sol b/src/strategies/polygon/sushiswap/strategy-sushi-eth-usdt-lp.sol deleted file mode 100644 index 264bfd5d5..000000000 --- a/src/strategies/polygon/sushiswap/strategy-sushi-eth-usdt-lp.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "../strategy-sushi-farm-base.sol"; - -contract StrategySushiEthUsdtLp is StrategySushiFarmBase { - // Token/ETH pool id in MasterChef contract - uint256 public sushi_usdt_poolId = 2; - // Token addresses - address public sushi_eth_usdt_lp = 0xc2755915a85C6f6c1C0F3a86ac8C058F11Caa9C9; - address public usdt = 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; - - constructor( - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategySushiFarmBase( - weth, - usdt, - sushi_usdt_poolId, - sushi_eth_usdt_lp, - _governance, - _strategist, - _controller, - _timelock - ) - {} - - // **** Views **** - - function getName() external override pure returns (string memory) { - return "StrategySushiEthUsdtLp"; - } -} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-matic-eth-lp.sol b/src/strategies/polygon/sushiswap/strategy-sushi-matic-eth-lp.sol deleted file mode 100644 index cf8458dfa..000000000 --- a/src/strategies/polygon/sushiswap/strategy-sushi-matic-eth-lp.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "../strategy-sushi-farm-base.sol"; - -contract StrategySushiMaticEthLp is StrategySushiFarmBase { - // Token/ETH pool id in MasterChef contract - uint256 public sushi_matic_eth_poolId = 0; - // Token addresses - address public sushi_matic_eth_lp = 0xc4e595acDD7d12feC385E5dA5D43160e8A0bAC0E; - - constructor( - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategySushiFarmBase( - wmatic, - weth, - sushi_matic_eth_poolId, - sushi_matic_eth_lp, - _governance, - _strategist, - _controller, - _timelock - ) - {} - - // **** Views **** - - function getName() external override pure returns (string memory) { - return "StrategySushiMaticEthLp"; - } -} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-matic-usdc-scplp.sol b/src/strategies/polygon/sushiswap/strategy-sushi-matic-usdc-scplp.sol new file mode 100644 index 000000000..d748c03d3 --- /dev/null +++ b/src/strategies/polygon/sushiswap/strategy-sushi-matic-usdc-scplp.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-polygon-base.sol"; + +contract StrategyPolySushiMaticUsdcScplp is StrategySushiPolyBase { + address private constant _lp = 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B; + uint256 private constant _pid = 60; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiPolyBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyPolySushiMaticUsdcScplp"; + } +} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-pickle-dai-lp.sol b/src/strategies/polygon/sushiswap/strategy-sushi-pickle-dai-lp.sol deleted file mode 100644 index af5cb4731..000000000 --- a/src/strategies/polygon/sushiswap/strategy-sushi-pickle-dai-lp.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; - -import "../strategy-sushi-farm-base.sol"; - -contract StrategySushiPickleDaiLp is StrategySushiFarmBase { - // Pickle/Dai pool id in MasterChef contract - uint256 public sushi_pickle_dai_poolId = 37; - - // Token addresses - address public pickle_dai_slp = 0x57602582eB5e82a197baE4E8b6B80E39abFC94EB; - address public dai = 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063; - address public pickle = 0x2b88aD57897A8b496595925F43048301C37615Da; - - constructor( - address _governance, - address _strategist, - address _controller, - address _timelock - ) - public - StrategySushiFarmBase( - pickle, - dai, - sushi_pickle_dai_poolId, - pickle_dai_slp, - _governance, - _strategist, - _controller, - _timelock - ) - {} - - // **** Views **** - - function getName() external pure override returns (string memory) { - return "StrategySushiPickleDaiLp"; - } - - // **** State Mutations **** - - function harvest() public override onlyBenevolent { - // Collects SUSHI tokens - IMiniChefV2(miniChef).harvest(poolId, address(this)); - uint256 _sushi = IERC20(sushi).balanceOf(address(this)); - if (_sushi > 0) { - _swapSushiswap(sushi, weth, _sushi); - } - - // Collect MATIC tokens - uint256 _wmatic = IERC20(wmatic).balanceOf(address(this)); - if (_wmatic > 0) { - _swapSushiswap(wmatic, weth, _wmatic); - } - - // Swap half WETH for pickle - uint256 _weth = IERC20(weth).balanceOf(address(this)); - if (_weth > 0) { - address[] memory pathPickle = new address[](3); - pathPickle[0] = weth; - pathPickle[1] = dai; - pathPickle[2] = pickle; - _swapSushiswapWithPath(pathPickle, _weth.div(2)); - } - - // Swap half WETH for dai - if (_weth > 0) { - address[] memory pathDai = new address[](2); - pathDai[0] = weth; - pathDai[1] = dai; - _swapSushiswapWithPath(pathDai, _weth.div(2)); - } - - // Adds in liquidity for token0/token1 - uint256 _pickle = IERC20(pickle).balanceOf(address(this)); - uint256 _dai = IERC20(dai).balanceOf(address(this)); - if (_pickle > 0 && _dai > 0) { - IERC20(pickle).safeApprove(sushiRouter, 0); - IERC20(pickle).safeApprove(sushiRouter, _pickle); - IERC20(dai).safeApprove(sushiRouter, 0); - IERC20(dai).safeApprove(sushiRouter, _dai); - - UniswapRouterV2(sushiRouter).addLiquidity( - pickle, - dai, - _pickle, - _dai, - 0, - 0, - address(this), - now + 60 - ); - - // Donates DUST - IERC20(pickle).transfer( - IController(controller).treasury(), - IERC20(pickle).balanceOf(address(this)) - ); - IERC20(dai).safeTransfer( - IController(controller).treasury(), - IERC20(dai).balanceOf(address(this)) - ); - } - - // We want to get back SUSHI LP tokens - _distributePerformanceFeesAndDeposit(); - } -} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-polygon-base.sol b/src/strategies/polygon/sushiswap/strategy-sushi-polygon-base.sol new file mode 100644 index 000000000..fc2e0e017 --- /dev/null +++ b/src/strategies/polygon/sushiswap/strategy-sushi-polygon-base.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "../../sushiswap/strategy-sushi-bento-base.sol"; + +abstract contract StrategySushiPolyBase is StrategySushiBentoBase { + address private constant _tridentRouter = 0x7A250C60Cde7A5Ca7B667209beAB5eA4E16eed67; + address private constant _sushiRouter = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; + address private constant _bento = 0x0319000133d3AdA02600f0875d2cf03D442C3367; + address private constant _minichef = 0x0769fd68dFb93167989C6f7254cd0D766Fb2841F; + address private constant _sushi = 0x0b3F868E0BE5597D5DB7fEB59E1CADBb0fdDa50a; + address private constant _native = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + + constructor( + address _want, + uint256 _poolId, + address _governance, + address _strategist, + address _controller, + address _timelock + ) + StrategySushiBentoBase( + _want, + _sushi, + _native, + _bento, + _tridentRouter, + _sushiRouter, + _minichef, + _poolId, + _governance, + _strategist, + _controller, + _timelock + ) + { + performanceTreasuryFee = 1000; + + // Sushi to native route + address[] memory _p = new address[](2); + _p[0] = sushi; + _p[1] = native; + bytes[] memory _encodedFullRouteArr = new bytes[](1); + _encodedFullRouteArr[0] = abi.encode(true, abi.encode(_p)); + _addToNativeRoute(abi.encode(_encodedFullRouteArr)); + } +} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-usdc-dai-sslp.sol b/src/strategies/polygon/sushiswap/strategy-sushi-usdc-dai-sslp.sol new file mode 100644 index 000000000..8edc6e2e5 --- /dev/null +++ b/src/strategies/polygon/sushiswap/strategy-sushi-usdc-dai-sslp.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-polygon-base.sol"; + +contract StrategyPolySushiUsdcDaiSslp is StrategySushiPolyBase { + address private constant _lp = 0x4440C62021c5a0d6feC1A2143914Be0c5A0A00EA; + uint256 private constant _pid = 63; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiPolyBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + ITridentRouter.Path[] memory _p0 = new ITridentRouter.Path[](1); + _p0[0] = ITridentRouter.Path({ + pool: 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(false, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](2); + _p1[0] = ITridentRouter.Path({ + pool: 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B, + data: abi.encode(native, 0x4440C62021c5a0d6feC1A2143914Be0c5A0A00EA, false) + }); + _p1[1] = ITridentRouter.Path({ + pool: 0x4440C62021c5a0d6feC1A2143914Be0c5A0A00EA, + data: abi.encode(token0, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyPolySushiUsdcDaiSslp"; + } +} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-usdc-eth-scplp.sol b/src/strategies/polygon/sushiswap/strategy-sushi-usdc-eth-scplp.sol new file mode 100644 index 000000000..7ee07e28f --- /dev/null +++ b/src/strategies/polygon/sushiswap/strategy-sushi-usdc-eth-scplp.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-polygon-base.sol"; + +contract StrategyPolySushiUsdcEthScplp is StrategySushiPolyBase { + address private constant _lp = 0x4D3222Ba7a87ce007903B0318037CE0f5ca9F15d; + uint256 private constant _pid = 61; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiPolyBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + ITridentRouter.Path[] memory _p0 = new ITridentRouter.Path[](1); + _p0[0] = ITridentRouter.Path({ + pool: 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(false, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + address[] memory _p1 = new address[](2); + _p1[0] = native; + _p1[1] = token1; + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(true, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyPolySushiUsdcEthScplp"; + } +} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-usdc-usdt-sslp.sol b/src/strategies/polygon/sushiswap/strategy-sushi-usdc-usdt-sslp.sol new file mode 100644 index 000000000..6d9aca9b5 --- /dev/null +++ b/src/strategies/polygon/sushiswap/strategy-sushi-usdc-usdt-sslp.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-polygon-base.sol"; + +contract StrategyPolySushiUsdcUsdtSslp is StrategySushiPolyBase { + address private constant _lp = 0x231BA46173b75E4D7cEa6DCE095A6c1c3E876270; + uint256 private constant _pid = 62; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiPolyBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + ITridentRouter.Path[] memory _p0 = new ITridentRouter.Path[](1); + _p0[0] = ITridentRouter.Path({ + pool: 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(false, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](2); + _p1[0] = ITridentRouter.Path({ + pool: 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B, + data: abi.encode(native, 0x231BA46173b75E4D7cEa6DCE095A6c1c3E876270, false) + }); + _p1[1] = ITridentRouter.Path({ + pool: 0x231BA46173b75E4D7cEa6DCE095A6c1c3E876270, + data: abi.encode(token0, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + _setRewarder(false); + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyPolySushiUsdcUsdtSslp"; + } +} diff --git a/src/strategies/polygon/sushiswap/strategy-sushi-usdplus-usdc-sslp.sol b/src/strategies/polygon/sushiswap/strategy-sushi-usdplus-usdc-sslp.sol new file mode 100644 index 000000000..f3c937a95 --- /dev/null +++ b/src/strategies/polygon/sushiswap/strategy-sushi-usdplus-usdc-sslp.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "./strategy-sushi-polygon-base.sol"; + +contract StrategyPolySushiUsdplusUsdcSslp is StrategySushiPolyBase { + address private constant _lp = 0x571102a88928d74C049849af094a289C27Fb794E; + uint256 private constant _pid = 58; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategySushiPolyBase(_lp, _pid, _governance, _strategist, _controller, _timelock) { + // Pool type + isBentoPool = true; + + // Native to token0 + ITridentRouter.Path[] memory _p0 = new ITridentRouter.Path[](2); + _p0[0] = ITridentRouter.Path({ + pool: 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B, + data: abi.encode(native, 0x571102a88928d74C049849af094a289C27Fb794E, false) + }); + _p0[1] = ITridentRouter.Path({ + pool: 0x571102a88928d74C049849af094a289C27Fb794E, + data: abi.encode(token1, address(this), true) + }); + bytes[] memory _encodedToken0Route = new bytes[](1); + _encodedToken0Route[0] = abi.encode(false, abi.encode(_p0)); + _addToTokenRoute(abi.encode(token0, _encodedToken0Route)); + + + // Native to token1 + ITridentRouter.Path[] memory _p1 = new ITridentRouter.Path[](1); + _p1[0] = ITridentRouter.Path({ + pool: 0x846Fea3D94976ef9862040d9FbA9C391Aa75A44B, + data: abi.encode(native, address(this), true) + }); + bytes[] memory _encodedToken1Route = new bytes[](1); + _encodedToken1Route[0] = abi.encode(false, abi.encode(_p1)); + _addToTokenRoute(abi.encode(token1, _encodedToken1Route)); + + // Approvals + if (isBentoPool == true) { + IERC20(token0).approve(bentoBox, type(uint256).max); + IERC20(token1).approve(bentoBox, type(uint256).max); + } else { + IERC20(token0).approve(sushiRouter, type(uint256).max); + IERC20(token1).approve(sushiRouter, type(uint256).max); + } + + // Reward to native route + } + + // **** Views **** + + function getName() external pure override returns (string memory) { + return "StrategyPolySushiUsdplusUsdcSslp"; + } +} diff --git a/src/strategies/polygon/uniswapv3/strategy-univ3-matic-eth-lp.sol b/src/strategies/polygon/uniswapv3/strategy-univ3-matic-eth-lp.sol index fdf19c157..1b50de92a 100644 --- a/src/strategies/polygon/uniswapv3/strategy-univ3-matic-eth-lp.sol +++ b/src/strategies/polygon/uniswapv3/strategy-univ3-matic-eth-lp.sol @@ -1,22 +1,24 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance-staker.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; -contract StrategyMaticEthUniV3StakerPoly is StrategyRebalanceStakerUniV3 { +contract StrategyMaticEthUniV3Poly is StrategyRebalanceUniV3 { address private priv_pool = 0x167384319B41F7094e62f7506409Eb38079AbfF8; + address private constant wmatic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + address private constant weth = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; constructor( int24 _tickRangeMultiplier, - uint24 _swapPoolFee, address _governance, address _strategist, address _controller, address _timelock ) public - StrategyRebalanceStakerUniV3( + StrategyRebalanceUniV3( + wmatic, priv_pool, _tickRangeMultiplier, _governance, @@ -25,18 +27,7 @@ contract StrategyMaticEthUniV3StakerPoly is StrategyRebalanceStakerUniV3 { _timelock ) { - univ3_staker = 0x1f98407aaB862CdDeF78Ed252D6f557aA5b0f00d; - rewardToken = 0x0000000000000000000000000000000000000000; - - key = IUniswapV3Staker.IncentiveKey({ - rewardToken: IERC20Minimal(rewardToken), - pool: IUniswapV3Pool(priv_pool), - startTime: 0, - endTime: 1, - refundee: 0x0000000000000000000000000000000000000000 - }); - - swapPoolFee = (_swapPoolFee != 0) ? _swapPoolFee : pool.fee(); + tokenToNativeRoutes[weth] = abi.encodePacked(weth, uint24(500), wmatic); } function getName() external pure override returns (string memory) { diff --git a/src/strategies/polygon/uniswapv3/strategy-univ3-matic-usdc-lp.sol b/src/strategies/polygon/uniswapv3/strategy-univ3-matic-usdc-lp.sol index 7588c9e54..c37f6840d 100644 --- a/src/strategies/polygon/uniswapv3/strategy-univ3-matic-usdc-lp.sol +++ b/src/strategies/polygon/uniswapv3/strategy-univ3-matic-usdc-lp.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance-staker.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; -contract StrategyMaticUsdcUniV3StakerPoly is StrategyRebalanceStakerUniV3 { +contract StrategyMaticUsdcUniV3Poly is StrategyRebalanceUniV3 { address private priv_pool = 0x88f3C15523544835fF6c738DDb30995339AD57d6; + address private constant wmatic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + address private constant usdc = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; constructor( int24 _tickRangeMultiplier, @@ -15,7 +17,8 @@ contract StrategyMaticUsdcUniV3StakerPoly is StrategyRebalanceStakerUniV3 { address _timelock ) public - StrategyRebalanceStakerUniV3( + StrategyRebalanceUniV3( + wmatic, priv_pool, _tickRangeMultiplier, _governance, @@ -24,16 +27,7 @@ contract StrategyMaticUsdcUniV3StakerPoly is StrategyRebalanceStakerUniV3 { _timelock ) { - univ3_staker = 0x1f98407aaB862CdDeF78Ed252D6f557aA5b0f00d; - rewardToken = 0x0000000000000000000000000000000000000000; - - key = IUniswapV3Staker.IncentiveKey({ - rewardToken: IERC20Minimal(rewardToken), - pool: IUniswapV3Pool(priv_pool), - startTime: 0, - endTime: 1, - refundee: 0x0000000000000000000000000000000000000000 - }); + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(3000), wmatic); } function getName() external pure override returns (string memory) { diff --git a/src/strategies/polygon/uniswapv3/strategy-univ3-usdc-eth-lp.sol b/src/strategies/polygon/uniswapv3/strategy-univ3-usdc-eth-lp.sol index d7b2c36d6..c3a27d1e5 100644 --- a/src/strategies/polygon/uniswapv3/strategy-univ3-usdc-eth-lp.sol +++ b/src/strategies/polygon/uniswapv3/strategy-univ3-usdc-eth-lp.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance-staker.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; -contract StrategyUsdcEthUniV3StakerPoly is StrategyRebalanceStakerUniV3 { - address private usdc_eth_pool = 0x45dDa9cb7c25131DF268515131f647d726f50608; +contract StrategyUsdcEthUniV3Poly is StrategyRebalanceUniV3 { + address private priv_pool = 0x45dDa9cb7c25131DF268515131f647d726f50608; + address private constant wmatic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + address private constant usdc = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; + address private constant weth = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; constructor( int24 _tickRangeMultiplier, @@ -15,8 +18,9 @@ contract StrategyUsdcEthUniV3StakerPoly is StrategyRebalanceStakerUniV3 { address _timelock ) public - StrategyRebalanceStakerUniV3( - usdc_eth_pool, + StrategyRebalanceUniV3( + wmatic, + priv_pool, _tickRangeMultiplier, _governance, _strategist, @@ -24,16 +28,8 @@ contract StrategyUsdcEthUniV3StakerPoly is StrategyRebalanceStakerUniV3 { _timelock ) { - univ3_staker = 0x1f98407aaB862CdDeF78Ed252D6f557aA5b0f00d; - rewardToken = 0x0000000000000000000000000000000000000000; - - key = IUniswapV3Staker.IncentiveKey({ - rewardToken: IERC20Minimal(rewardToken), - pool: IUniswapV3Pool(usdc_eth_pool), - startTime: 0, - endTime: 1, - refundee: 0x0000000000000000000000000000000000000000 - }); + tokenToNativeRoutes[weth] = abi.encodePacked(weth, uint24(500), wmatic); + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(3000), wmatic); } function getName() external pure override returns (string memory) { diff --git a/src/strategies/polygon/uniswapv3/strategy-univ3-usdc-usdt-lp.sol b/src/strategies/polygon/uniswapv3/strategy-univ3-usdc-usdt-lp.sol index 132e4bc2d..963ef6555 100644 --- a/src/strategies/polygon/uniswapv3/strategy-univ3-usdc-usdt-lp.sol +++ b/src/strategies/polygon/uniswapv3/strategy-univ3-usdc-usdt-lp.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance-staker.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; -contract StrategyUsdcUsdtUniV3StakerPoly is StrategyRebalanceStakerUniV3 { +contract StrategyUsdcUsdtUniV3Poly is StrategyRebalanceUniV3 { address private priv_pool = 0x3F5228d0e7D75467366be7De2c31D0d098bA2C23; + address private constant wmatic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + address private constant usdc = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; + address private constant usdt = 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; constructor( int24 _tickRangeMultiplier, @@ -15,7 +18,8 @@ contract StrategyUsdcUsdtUniV3StakerPoly is StrategyRebalanceStakerUniV3 { address _timelock ) public - StrategyRebalanceStakerUniV3( + StrategyRebalanceUniV3( + wmatic, priv_pool, _tickRangeMultiplier, _governance, @@ -24,16 +28,9 @@ contract StrategyUsdcUsdtUniV3StakerPoly is StrategyRebalanceStakerUniV3 { _timelock ) { - univ3_staker = 0x1f98407aaB862CdDeF78Ed252D6f557aA5b0f00d; - rewardToken = 0x0000000000000000000000000000000000000000; - - key = IUniswapV3Staker.IncentiveKey({ - rewardToken: IERC20Minimal(rewardToken), - pool: IUniswapV3Pool(priv_pool), - startTime: 0, - endTime: 1, - refundee: 0x0000000000000000000000000000000000000000 - }); + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(3000), wmatic); + tokenToNativeRoutes[usdt] = abi.encodePacked(usdt, uint24(500), usdc, uint24(3000), wmatic); + performanceTreasuryFee = 1000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/polygon/uniswapv3/strategy-univ3-wbtc-eth-lp.sol b/src/strategies/polygon/uniswapv3/strategy-univ3-wbtc-eth-lp.sol index 173436922..f93efb92e 100644 --- a/src/strategies/polygon/uniswapv3/strategy-univ3-wbtc-eth-lp.sol +++ b/src/strategies/polygon/uniswapv3/strategy-univ3-wbtc-eth-lp.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; -import "../strategy-univ3-rebalance-staker.sol"; +import "../../uniswapv3/strategy-univ3-rebalance.sol"; -contract StrategyWbtcEthUniV3StakerPoly is StrategyRebalanceStakerUniV3 { +contract StrategyWbtcEthUniV3Poly is StrategyRebalanceUniV3 { address private priv_pool = 0x50eaEDB835021E4A108B7290636d62E9765cc6d7; + address private constant wmatic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + address private constant weth = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; + address private constant wbtc = 0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6; constructor( int24 _tickRangeMultiplier, @@ -15,7 +18,8 @@ contract StrategyWbtcEthUniV3StakerPoly is StrategyRebalanceStakerUniV3 { address _timelock ) public - StrategyRebalanceStakerUniV3( + StrategyRebalanceUniV3( + wmatic, priv_pool, _tickRangeMultiplier, _governance, @@ -24,16 +28,8 @@ contract StrategyWbtcEthUniV3StakerPoly is StrategyRebalanceStakerUniV3 { _timelock ) { - univ3_staker = 0x1f98407aaB862CdDeF78Ed252D6f557aA5b0f00d; - rewardToken = 0x0000000000000000000000000000000000000000; - - key = IUniswapV3Staker.IncentiveKey({ - rewardToken: IERC20Minimal(rewardToken), - pool: IUniswapV3Pool(priv_pool), - startTime: 0, - endTime: 1, - refundee: 0x0000000000000000000000000000000000000000 - }); + tokenToNativeRoutes[weth] = abi.encodePacked(weth, uint24(500), wmatic); + tokenToNativeRoutes[wbtc] = abi.encodePacked(wbtc, uint24(500), weth, uint24(500), wmatic); } function getName() external pure override returns (string memory) { diff --git a/src/strategies/saddle/strategy-saddle-d4-v3.sol b/src/strategies/saddle/strategy-saddle-d4-v3.sol new file mode 100644 index 000000000..3c7742fab --- /dev/null +++ b/src/strategies/saddle/strategy-saddle-d4-v3.sol @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "../strategy-base-v2.sol"; + +import {ICurveGauge, ICurveMintr} from "../../interfaces/curve.sol"; +import {IFraxswapRouterMultihop} from "../../interfaces/fraxswap-router.sol"; +import {UniswapRouterV2} from "../../interfaces/uniswapv2.sol"; + +interface SwapFlashLoan { + function addLiquidity( + uint256[] calldata amounts, + uint256 minToMint, + uint256 deadline + ) external; +} + +contract StrategySaddleD4 is StrategyBase { + using SafeMath for uint256; + + // Tokens + address private frax = 0x853d955aCEf822Db058eb8505911ED77F175b99e; + address private reward = 0xf1Dc500FdE233A4055e25e5BbF516372BC4F6871; // sdl token + address private constant _native = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + // Protocol info + address public constant gauge = 0x702c1b8Ec3A77009D5898e18DA8F8959B6dF2093; + address public constant minter = 0x358fE82370a1B9aDaE2E3ad69D6cF9e503c96018; + address private constant saddle_d4lp = 0xd48cF4D7FB0824CC8bAe055dF3092584d0a1726A; + address private flashLoan = 0xC69DDcd4DFeF25D8a793241834d4cc4b3668EAD6; + + address private constant fraxswapRouterMultihop = 0x25e9acA5951262241290841b6f863d59D37DC4f0; + address private constant sushiRouter = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F; + + mapping(address => IFraxswapRouterMultihop.FraxswapParams) private nativeToTokenRoutes; + mapping(address => address[]) private toNativeRoutes; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyBase(saddle_d4lp, _native, _governance, _strategist, _controller, _timelock) { + // Performance fees + performanceTreasuryFee = 2000; + + // Approvals + IERC20(native).approve(fraxswapRouterMultihop, type(uint256).max); + IERC20(frax).approve(flashLoan, type(uint256).max); + IERC20(want).approve(gauge, type(uint256).max); + + // Native to Frax + IFraxswapRouterMultihop.FraxswapStepData memory _step = IFraxswapRouterMultihop.FraxswapStepData({ + swapType: 0, + directFundNextPool: 0, + directFundThisPool: 0, + tokenOut: frax, + pool: 0x31351Bf3fba544863FBff44DDC27bA880916A199, + extraParam1: 1, + extraParam2: 0, + percentOfHop: 10000 + }); + + bytes[] memory _steps = new bytes[](1); + _steps[0] = abi.encode(_step); + + IFraxswapRouterMultihop.FraxswapRoute memory _nextRoute = IFraxswapRouterMultihop.FraxswapRoute({ + tokenOut: frax, + amountOut: 0, + percentOfHop: 10000, + steps: _steps, + nextHops: new bytes[](0) + }); + + bytes[] memory _nextHops = new bytes[](1); + _nextHops[0] = abi.encode(_nextRoute); + + IFraxswapRouterMultihop.FraxswapRoute memory _route = IFraxswapRouterMultihop.FraxswapRoute({ + tokenOut: address(0), + amountOut: 0, + percentOfHop: 10000, + steps: new bytes[](0), + nextHops: _nextHops + }); + + IFraxswapRouterMultihop.FraxswapParams memory _params = IFraxswapRouterMultihop.FraxswapParams({ + tokenIn: native, + amountIn: 0, + tokenOut: frax, + amountOutMinimum: 0, + recipient: address(this), + deadline: block.timestamp, + approveMax: false, + v: 0, + r: "0x", + s: "0x", + route: abi.encode(_route) + }); + _addToTokenRoute(abi.encode(frax, _params)); + + // Reward to native + address[] memory _rewardToNative = new address[](2); + _rewardToNative[0] = reward; + _rewardToNative[1] = native; + _addToNativeRoute(abi.encode(_rewardToNative)); + } + + function getName() external pure override returns (string memory) { + return "StrategySaddleD4v2"; + } + + function balanceOfPool() public view override returns (uint256) { + return ICurveGauge(gauge).balanceOf(address(this)); + } + + /// @notice callStatic on this + function getHarvestableStatic() external override returns (address[] memory, uint256[] memory) { + address[] memory rewardTokens = new address[](1); + uint256[] memory pendingRewards = new uint256[](1); + + rewardTokens[0] = reward; + pendingRewards[0] = ICurveGauge(gauge).claimable_tokens(address(this)); + + return (rewardTokens, pendingRewards); + } + + function getToNativeRoute(address _token) external view returns (bytes memory route) { + route = abi.encode(toNativeRoutes[_token]); + } + + function getToTokenRoute(address _token) external view returns (bytes memory route) { + route = abi.encode(nativeToTokenRoutes[_token]); + } + + // **** Setters **** + + // **** State mutations **** // + + function deposit() public override { + uint256 _want = IERC20(want).balanceOf(address(this)); + if (_want > 0) { + ICurveGauge(gauge).deposit(_want); + } + } + + function _withdrawSome(uint256 _amount) internal override returns (uint256) { + ICurveGauge(gauge).withdraw(_amount); + return _amount; + } + + function harvest() public override { + // Collects rewards tokens + _harvestReward(); + + // loop through all rewards tokens and swap the ones with toNativeRoutes. + _swapActiveRewardsToNative(); + + // Collect Fees + _distributePerformanceFeesNative(); + + uint256 _nativeBalance = IERC20(native).balanceOf(address(this)); + if (_nativeBalance == 0) { + return; + } + + // Swap native to frax + _swapNativeToDeposit(_nativeBalance); + + // Adds in liquidity + _addLiquidity(); + + // Stake + deposit(); + } + + function _harvestReward() internal { + ICurveGauge(gauge).claim_rewards(address(this)); + ICurveMintr(minter).mint(address(gauge)); + } + + function _swapActiveRewardsToNative() internal { + for (uint256 i = 0; i < activeRewardsTokens.length; i++) { + uint256 _rewardToken = IERC20(activeRewardsTokens[i]).balanceOf(address(this)); + if (toNativeRoutes[activeRewardsTokens[i]].length > 0 && _rewardToken > 0) { + _swapSushiswapWithPath(toNativeRoutes[activeRewardsTokens[i]], _rewardToken); + } + } + } + + function _swapNativeToDeposit(uint256 _amountIn) internal { + IFraxswapRouterMultihop.FraxswapParams memory _params = nativeToTokenRoutes[frax]; + _params.amountIn = _amountIn; + _params.deadline = block.timestamp.add(60); + + IFraxswapRouterMultihop(fraxswapRouterMultihop).swap(_params); + } + + function _addLiquidity() internal { + uint256[] memory amounts = new uint256[](4); + amounts[2] = IERC20(frax).balanceOf(address(this)); + SwapFlashLoan(flashLoan).addLiquidity(amounts, 0, block.timestamp); + } + + function _swapSushiswapWithPath(address[] memory path, uint256 _amount) internal { + require(path[1] != address(0)); + + UniswapRouterV2(sushiRouter).swapExactTokensForTokens(_amount, 0, path, address(this), block.timestamp.add(60)); + } + + /// @notice this works exclusively with Fraxswap multihop router v2 + function _addToTokenRoute(bytes memory _route) internal override { + (address _token, IFraxswapRouterMultihop.FraxswapParams memory _params) = abi.decode( + _route, + (address, IFraxswapRouterMultihop.FraxswapParams) + ); + + // Delete the old route + delete nativeToTokenRoutes[_token]; + + nativeToTokenRoutes[_token] = _params; + } + + /// @notice this works exclusively with Sushiswap legacy router + function _addToNativeRoute(bytes memory _route) internal override { + address[] memory _path = abi.decode(_route, (address[])); + + address _token = _path[0]; + + if (toNativeRoutes[_token].length == 0) { + activeRewardsTokens.push(_token); + } else { + delete toNativeRoutes[_token]; + } + + IERC20(_token).approve(sushiRouter, type(uint256).max); + toNativeRoutes[_token] = _path; + } + + function getHarvestable() external view override returns (address[] memory, uint256[] memory) { + address[] memory rewardTokens = new address[](1); + uint256[] memory pendingRewards = new uint256[](1); + + rewardTokens[0] = reward; + pendingRewards[0] = 0; + + return (rewardTokens, pendingRewards); + } +} diff --git a/src/strategies/strategy-base-v2.sol b/src/strategies/strategy-base-v2.sol index e4e97c0f4..886fb5ccf 100644 --- a/src/strategies/strategy-base-v2.sol +++ b/src/strategies/strategy-base-v2.sol @@ -2,9 +2,6 @@ pragma solidity >=0.8.6; import "../lib/erc20.sol"; - -import "../interfaces/jar.sol"; -import "../interfaces/uniswapv2.sol"; import "../interfaces/controller.sol"; // Strategy Contract Basics @@ -14,55 +11,46 @@ abstract contract StrategyBase { using Address for address; using SafeMath for uint256; - // Perfomance fees - start with 20% - uint256 public performanceTreasuryFee = 2000; + // Perfomance fees + uint256 public performanceTreasuryFee = 0; uint256 public constant performanceTreasuryMax = 10000; - uint256 public performanceDevFee = 0; - uint256 public constant performanceDevMax = 10000; - - // Withdrawal fee 0% - // - 0% to treasury - // - 0% to dev fund + // Withdrawal fee uint256 public withdrawalTreasuryFee = 0; uint256 public constant withdrawalTreasuryMax = 100000; - uint256 public withdrawalDevFundFee = 0; - uint256 public constant withdrawalDevFundMax = 100000; - // Tokens - address public want; - address public constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - address public constant native = weth; + address public immutable want; + address public immutable native; + address[] public activeRewardsTokens; - // User accounts + // Permissioned accounts address public governance; address public controller; address public strategist; address public timelock; - - // Dex - address public constant uniV2Router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; - address public constant univ3Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564; - address public constant sushiRouter = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F; - + address public pendingTimelock; mapping(address => bool) public harvesters; - address[] public activeRewardsTokens; constructor( address _want, + address _native, address _governance, address _strategist, address _controller, address _timelock ) { + // Sanity checks require(_want != address(0)); + require(_native != address(0)); require(_governance != address(0)); require(_strategist != address(0)); require(_controller != address(0)); require(_timelock != address(0)); + // Constants assignments want = _want; + native = _native; governance = _governance; strategist = _strategist; controller = _controller; @@ -71,8 +59,31 @@ abstract contract StrategyBase { // **** Modifiers **** // - modifier onlyBenevolent() { - require(harvesters[msg.sender] || msg.sender == governance || msg.sender == strategist); + modifier onlyHarvester() { + require( + harvesters[msg.sender] || msg.sender == strategist || msg.sender == governance || msg.sender == timelock, + "!harvester" + ); + _; + } + + modifier onlyStrategist() { + require(msg.sender == strategist || msg.sender == governance || msg.sender == timelock, "!strategist"); + _; + } + + modifier onlyGovernance() { + require(msg.sender == governance || msg.sender == timelock, "!governance"); + _; + } + + modifier onlyTimelock() { + require(msg.sender == timelock, "!timelock"); + _; + } + + modifier onlyController() { + require(msg.sender == controller, "!controller"); _; } @@ -90,7 +101,12 @@ abstract contract StrategyBase { function getName() external pure virtual returns (string memory); - function getHarvestable() external view virtual returns (uint256); + function getHarvestable() external view virtual returns (address[] memory, uint256[] memory); + + /// @notice should be overridden if it needs to be used with callStatic + function getHarvestableStatic() external virtual returns (address[] memory, uint256[] memory) { + return this.getHarvestable(); + } function getActiveRewardsTokens() external view returns (address[] memory) { return activeRewardsTokens; @@ -98,109 +114,97 @@ abstract contract StrategyBase { // **** Setters **** // - function whitelistHarvesters(address[] calldata _harvesters) external { - require(msg.sender == governance || msg.sender == strategist || harvesters[msg.sender], "not authorized"); - + function whitelistHarvesters(address[] calldata _harvesters) external onlyHarvester { for (uint256 i = 0; i < _harvesters.length; i++) { harvesters[_harvesters[i]] = true; } } - function revokeHarvesters(address[] calldata _harvesters) external { - require(msg.sender == governance || msg.sender == strategist, "not authorized"); - + function revokeHarvesters(address[] calldata _harvesters) external onlyStrategist { for (uint256 i = 0; i < _harvesters.length; i++) { harvesters[_harvesters[i]] = false; } } - function setWithdrawalDevFundFee(uint256 _withdrawalDevFundFee) external { - require(msg.sender == timelock, "!timelock"); - withdrawalDevFundFee = _withdrawalDevFundFee; - } - - function setWithdrawalTreasuryFee(uint256 _withdrawalTreasuryFee) external { - require(msg.sender == timelock, "!timelock"); + function setWithdrawalTreasuryFee(uint256 _withdrawalTreasuryFee) external onlyTimelock { withdrawalTreasuryFee = _withdrawalTreasuryFee; } - function setPerformanceDevFee(uint256 _performanceDevFee) external { - require(msg.sender == timelock, "!timelock"); - performanceDevFee = _performanceDevFee; - } - - function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) external { - require(msg.sender == timelock, "!timelock"); + function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) external onlyTimelock { performanceTreasuryFee = _performanceTreasuryFee; } - function setStrategist(address _strategist) external { - require(msg.sender == governance, "!governance"); + function setStrategist(address _strategist) external onlyGovernance { strategist = _strategist; } - function setGovernance(address _governance) external { - require(msg.sender == governance, "!governance"); + function setGovernance(address _governance) external onlyGovernance { governance = _governance; } - function setTimelock(address _timelock) external { - require(msg.sender == timelock, "!timelock"); - timelock = _timelock; + function setPendingTimelock(address _pendingTimelock) external onlyTimelock { + pendingTimelock = _pendingTimelock; } - function setController(address _controller) external { - require(msg.sender == timelock, "!timelock"); + function acceptTimelock() external { + require(msg.sender == pendingTimelock, "!pendingTimelock"); + timelock = pendingTimelock; + pendingTimelock = address(0); + } + + function setController(address _controller) external onlyTimelock { controller = _controller; } // **** State mutations **** // + + // Adds/updates a swap path from a token to native, normally used for adding/updating a reward path + function addToNativeRoute(bytes calldata _route) external onlyStrategist { + _addToNativeRoute(_route); + } + + function addToTokenRoute(bytes calldata _route) external onlyStrategist { + _addToTokenRoute(_route); + } + + function _addToTokenRoute(bytes memory _route) internal virtual; + + function _addToNativeRoute(bytes memory _route) internal virtual; + + function deactivateReward(address _reward) external onlyStrategist { + for (uint256 i = 0; i < activeRewardsTokens.length; i++) { + if (activeRewardsTokens[i] == _reward) { + activeRewardsTokens[i] = activeRewardsTokens[activeRewardsTokens.length - 1]; + activeRewardsTokens.pop(); + } + } + } + function deposit() public virtual; // Controller only function for creating additional rewards from dust - function withdraw(IERC20 _asset) external returns (uint256 balance) { - require(msg.sender == controller, "!controller"); + function withdraw(IERC20 _asset) external onlyController returns (uint256 balance) { require(want != address(_asset), "want"); balance = _asset.balanceOf(address(this)); _asset.safeTransfer(controller, balance); } // Withdraw partial funds, normally used with a jar withdrawal - function withdraw(uint256 _amount) external { - require(msg.sender == controller, "!controller"); + function withdraw(uint256 _amount) external onlyController { uint256 _balance = IERC20(want).balanceOf(address(this)); if (_balance < _amount) { _amount = _withdrawSome(_amount.sub(_balance)); _amount = _amount.add(_balance); } - uint256 _feeDev = _amount.mul(withdrawalDevFundFee).div(withdrawalDevFundMax); - IERC20(want).safeTransfer(IController(controller).devfund(), _feeDev); - - uint256 _feeTreasury = _amount.mul(withdrawalTreasuryFee).div(withdrawalTreasuryMax); - IERC20(want).safeTransfer(IController(controller).treasury(), _feeTreasury); - address _jar = IController(controller).jars(address(want)); require(_jar != address(0), "!jar"); // additional protection so we don't burn the funds - IERC20(want).safeTransfer(_jar, _amount.sub(_feeDev).sub(_feeTreasury)); - } - - // Withdraw funds, used to swap between strategies - function withdrawForSwap(uint256 _amount) external returns (uint256 balance) { - require(msg.sender == controller, "!controller"); - _withdrawSome(_amount); - - balance = IERC20(want).balanceOf(address(this)); - - address _jar = IController(controller).jars(address(want)); - require(_jar != address(0), "!jar"); - IERC20(want).safeTransfer(_jar, balance); + IERC20(want).safeTransfer(_jar, _amount); } // Withdraw all funds, normally used when migrating strategies - function withdrawAll() external returns (uint256 balance) { - require(msg.sender == controller, "!controller"); + function withdrawAll() external onlyController returns (uint256 balance) { _withdrawAll(); balance = IERC20(want).balanceOf(address(this)); @@ -220,8 +224,7 @@ abstract contract StrategyBase { // **** Emergency functions **** - function execute(address _target, bytes memory _data) public payable returns (bytes memory response) { - require(msg.sender == timelock, "!timelock"); + function execute(address _target, bytes memory _data) public payable onlyTimelock returns (bytes memory response) { require(_target != address(0), "!target"); // call contract in current context @@ -242,20 +245,6 @@ abstract contract StrategyBase { } } - function _swapDefaultWithPath(address[] memory path, uint256 _amount) internal { - require(path[1] != address(0)); - UniswapRouterV2(uniV2Router).swapExactTokensForTokens(_amount, 0, path, address(this), block.timestamp.add(60)); - } - - function _swapWithPath( - address router, - address[] memory path, - uint256 _amount - ) internal { - require(path[1] != address(0)); - UniswapRouterV2(router).swapExactTokensForTokens(_amount, 0, path, address(this), block.timestamp.add(60)); - } - function _distributePerformanceFeesNative() internal { uint256 _native = IERC20(native).balanceOf(address(this)); if (_native > 0) { diff --git a/src/strategies/sushiswap/strategy-sushi-bento-base.sol b/src/strategies/sushiswap/strategy-sushi-bento-base.sol new file mode 100644 index 000000000..f991357be --- /dev/null +++ b/src/strategies/sushiswap/strategy-sushi-bento-base.sol @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.6; + +import "../strategy-base-v2.sol"; + +import "../../interfaces/minichefv2.sol"; +import "../../interfaces/masterchef-rewarder.sol"; +import "../../interfaces/trident-router.sol"; +import "../../interfaces/uniswapv2.sol"; +import "../../interfaces/bentobox.sol"; + +// Strategy Contract Basics + +abstract contract StrategySushiBentoBase is StrategyBase { + struct RouteStep { + bool isLegacy; + address[] legacyPath; + ITridentRouter.Path[] tridentPath; + } + + // Tokens + address public immutable token0; + address public immutable token1; + address public immutable sushi; + address public reward; + + // Protocol info + address public immutable tridentRouter; + address public immutable sushiRouter; + address public immutable bentoBox; + address public immutable minichef; + uint256 public immutable poolId; + address public rewarder; + bool public isBentoPool; + + mapping(address => RouteStep[]) private nativeToTokenRoutes; + mapping(address => RouteStep[]) private toNativeRoutes; + + constructor( + address _want, + address _sushi, + address _native, + address _bento, + address _tridentRouter, + address _sushiRouter, + address _minichef, + uint256 _poolId, + address _governance, + address _strategist, + address _controller, + address _timelock + ) StrategyBase(_want, _native, _governance, _strategist, _controller, _timelock) { + // Sanity checks + require(_sushi != address(0)); + require(_bento != address(0)); + require(_minichef != address(0)); + + // Constants assignments + tridentRouter = _tridentRouter; + sushiRouter = _sushiRouter; + bentoBox = _bento; + minichef = _minichef; + poolId = _poolId; + token0 = ITridentPair(_want).token0(); + token1 = ITridentPair(_want).token1(); + sushi = _sushi; + + // Approvals + if (_tridentRouter != address(0)) { + IBentoBox(_bento).setMasterContractApproval(address(this), _tridentRouter, true, 0, "", ""); + } + IERC20(_want).approve(_minichef, type(uint256).max); + } + + // **** Views **** // + + function balanceOfPool() public view override returns (uint256 amount) { + (amount, ) = IMiniChefV2(minichef).userInfo(poolId, address(this)); + } + + function getHarvestable() external view override returns (address[] memory, uint256[] memory) { + uint256 length; + if (rewarder == address(0)) { + length = 1; + } else { + length = 2; + } + + address[] memory rewardTokens = new address[](length); + uint256[] memory pendingRewards = new uint256[](length); + + rewardTokens[0] = sushi; + pendingRewards[0] = IMiniChefV2(minichef).pendingSushi(poolId, address(this)); + + if (length > 1) { + (IERC20[] memory rewarderTokens, uint256[] memory rewarderAmounts) = IMasterchefRewarder(rewarder) + .pendingTokens(poolId, address(this), 0); + rewardTokens[1] = address(rewarderTokens[0]); + pendingRewards[1] = rewarderAmounts[0]; + } + + return (rewardTokens, pendingRewards); + } + + function getToNativeRouteLength(address _rewardToken) external view returns (uint256) { + return toNativeRoutes[_rewardToken].length; + } + + function getToTokenRouteLength(address _token) external view returns (uint256) { + return nativeToTokenRoutes[_token].length; + } + + function getToNativeRoute(address _token, uint256 _index) + external + view + returns ( + bool isLegacy, + address[] memory legacyPath, + ITridentRouter.Path[] memory tridentPath + ) + { + isLegacy = toNativeRoutes[_token][_index].isLegacy; + legacyPath = toNativeRoutes[_token][_index].legacyPath; + tridentPath = toNativeRoutes[_token][_index].tridentPath; + } + + function getToTokenRoute(address _token, uint256 _index) + external + view + returns ( + bool isLegacy, + address[] memory legacyPath, + ITridentRouter.Path[] memory tridentPath + ) + { + isLegacy = nativeToTokenRoutes[_token][_index].isLegacy; + legacyPath = nativeToTokenRoutes[_token][_index].legacyPath; + tridentPath = nativeToTokenRoutes[_token][_index].tridentPath; + } + + // **** Setters **** // + + function setRewarder(bool _disable) external onlyStrategist { + _setRewarder(_disable); + } + + function _setRewarder(bool _disable) internal { + if (_disable == true) { + rewarder = address(0); + reward = address(0); + } else { + address _rewarder = address(IMiniChefV2(minichef).rewarder(poolId)); + (IERC20[] memory rewardTokens, ) = IMasterchefRewarder(_rewarder).pendingTokens(poolId, address(this), 0); + reward = address(rewardTokens[0]); + } + } + + // **** State mutations **** // + function deposit() public override { + uint256 _want = IERC20(want).balanceOf(address(this)); + if (_want > 0) { + IMiniChefV2(minichef).deposit(poolId, _want, address(this)); + } + } + + function _withdrawSome(uint256 _amount) internal override returns (uint256) { + IMiniChefV2(minichef).withdraw(poolId, _amount, address(this)); + return _amount; + } + + function _addToNativeRoute(bytes memory path) internal override { + bytes[] memory _routesEncodedArr = abi.decode(path, (bytes[])); + (bool _isLegacy, bytes memory _tokenPath) = abi.decode(_routesEncodedArr[0], (bool, bytes)); + + // Add token to activeRewardsTokens list + address _token; + if (_isLegacy == true) { + address[] memory _path = abi.decode(_tokenPath, (address[])); + _token = _path[0]; + } else { + ITridentRouter.Path[] memory _path = abi.decode(_tokenPath, (ITridentRouter.Path[])); + (_token, , ) = abi.decode(_path[0].data, (address, address, bool)); + } + + if (toNativeRoutes[_token].length == 0) { + activeRewardsTokens.push(_token); + } else { + delete toNativeRoutes[_token]; + } + + for (uint256 i = 0; i < _routesEncodedArr.length; i++) { + (bool _isLegacy1, bytes memory _pathEncoded) = abi.decode(_routesEncodedArr[0], (bool, bytes)); + + // Set allowance for router/bento + if (_isLegacy1 == true) { + address[] memory _path = abi.decode(_pathEncoded, (address[])); + IERC20(_path[0]).approve(sushiRouter, type(uint256).max); + + toNativeRoutes[_token].push(); + toNativeRoutes[_token][i].isLegacy = true; + toNativeRoutes[_token][i].legacyPath = _path; + } else { + ITridentRouter.Path[] memory _path = abi.decode(_pathEncoded, (ITridentRouter.Path[])); + (address tokenIn, , ) = abi.decode(_path[0].data, (address, address, bool)); + IERC20(tokenIn).approve(bentoBox, type(uint256).max); + + toNativeRoutes[_token].push(); + toNativeRoutes[_token][i].isLegacy = false; + for (uint256 j = 0; j < _path.length; j++) { + toNativeRoutes[_token][i].tridentPath.push(_path[j]); + } + } + } + } + + function _addToTokenRoute(bytes memory path) internal override { + (address token, bytes[] memory _encodedRoutes) = abi.decode(path, (address, bytes[])); + + // Delete the old route + if (nativeToTokenRoutes[token].length > 0) { + delete nativeToTokenRoutes[token]; + } + + for (uint256 i = 0; i < _encodedRoutes.length; i++) { + (bool _isLegacy, bytes memory _pathEncoded) = abi.decode(_encodedRoutes[i], (bool, bytes)); + + // Set allowance for router/bento + if (_isLegacy == true) { + address[] memory _path = abi.decode(_pathEncoded, (address[])); + IERC20(_path[0]).approve(sushiRouter, type(uint256).max); + + nativeToTokenRoutes[token].push(); + nativeToTokenRoutes[token][i].isLegacy = true; + nativeToTokenRoutes[token][i].legacyPath = _path; + } else { + ITridentRouter.Path[] memory _path = abi.decode(_pathEncoded, (ITridentRouter.Path[])); + (address tokenIn, , ) = abi.decode(_path[0].data, (address, address, bool)); + IERC20(tokenIn).approve(bentoBox, type(uint256).max); + + nativeToTokenRoutes[token].push(); + nativeToTokenRoutes[token][i].isLegacy = false; + for (uint256 j = 0; j < _path.length; j++) { + nativeToTokenRoutes[token][i].tridentPath.push(_path[j]); + } + } + } + } + + function _harvestReward() internal { + IMiniChefV2(minichef).harvest(poolId, address(this)); + } + + function _swapActiveRewardsToNative() internal { + for (uint256 i = 0; i < activeRewardsTokens.length; i++) { + uint256 _rewardToken = IERC20(activeRewardsTokens[i]).balanceOf(address(this)); + if (toNativeRoutes[activeRewardsTokens[i]].length > 0 && _rewardToken > 0) { + _swap(toNativeRoutes[activeRewardsTokens[i]], _rewardToken); + } + } + } + + function _swapNativeToDeposit(uint256 _native) internal { + if (native == token0 || native == token1) { + address toToken = native == token0 ? token1 : token0; + _swap(nativeToTokenRoutes[toToken], _native / 2); + } else { + // Swap native to token0 & token1 + uint256 _toToken0 = _native / 2; + uint256 _toToken1 = _native - _toToken0; + _swap(nativeToTokenRoutes[token0], _toToken0); + _swap(nativeToTokenRoutes[token1], _toToken1); + } + } + + function _addLiquidity() internal { + uint256 _token0 = IERC20(token0).balanceOf(address(this)); + uint256 _token1 = IERC20(token1).balanceOf(address(this)); + if (_token0 > 0 && _token1 > 0) { + if (isBentoPool == true) { + bytes memory data = abi.encode(address(this)); + ITridentRouter.TokenInput[] memory tokenInput = new ITridentRouter.TokenInput[](2); + tokenInput[0] = ITridentRouter.TokenInput({token: token0, native: true, amount: _token0}); + tokenInput[1] = ITridentRouter.TokenInput({token: token1, native: true, amount: _token1}); + ITridentRouter(tridentRouter).addLiquidity(tokenInput, want, 0, data); + } else { + UniswapRouterV2(sushiRouter).addLiquidity( + token0, + token1, + _token0, + _token1, + 0, + 0, + address(this), + block.timestamp + 60 + ); + } + } + } + + function harvest() public override { + // Collects rewards tokens + _harvestReward(); + + // loop through all rewards tokens and swap the ones with toNativeRoutes. + _swapActiveRewardsToNative(); + + // Collect Fees + _distributePerformanceFeesNative(); + + uint256 _native = IERC20(native).balanceOf(address(this)); + if (_native == 0) { + return; + } + + // Swap native to token0/token1 + _swapNativeToDeposit(_native); + + // Adds in liquidity for token0/token1 + _addLiquidity(); + + // Stake + deposit(); + } + + function _swap(RouteStep[] memory route, uint256 _amount) internal returns (uint256 _outputAmount) { + _outputAmount = _amount; + for (uint256 i = 0; i < route.length; i++) { + if (route[i].isLegacy == true) { + _outputAmount = _swapLegacyWithPath(route[i].legacyPath, _outputAmount); + } else { + _outputAmount = _swapTridentWithPath(route[i].tridentPath, _outputAmount); + } + } + } + + function _swapTridentWithPath(ITridentRouter.Path[] memory _path, uint256 _amount) + internal + returns (uint256 _outputAmount) + { + (address tokenIn, , ) = abi.decode(_path[0].data, (address, address, bool)); + _outputAmount = ITridentRouter(tridentRouter).exactInputWithNativeToken( + ITridentRouter.ExactInputParams({tokenIn: tokenIn, amountIn: _amount, amountOutMinimum: 0, path: _path}) + ); + } + + function _swapLegacyWithPath(address[] memory path, uint256 _amount) internal returns (uint256 _outputAmount) { + uint256[] memory _amountsOut = UniswapRouterV2(sushiRouter).swapExactTokensForTokens( + _amount, + 0, + path, + address(this), + block.timestamp + 60 + ); + _outputAmount = _amountsOut[_amountsOut.length - 1]; + } +} diff --git a/src/strategies/uniswapv3/strategy-univ3-ape-eth-lp.sol b/src/strategies/uniswapv3/strategy-univ3-ape-eth-lp.sol new file mode 100644 index 000000000..c0573f0ad --- /dev/null +++ b/src/strategies/uniswapv3/strategy-univ3-ape-eth-lp.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.12 <0.8.0; +pragma experimental ABIEncoderV2; + +import "./strategy-univ3-rebalance.sol"; + +contract StrategyApeEthUniV3 is StrategyRebalanceUniV3 { + address private priv_pool = 0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF; + address private ape = 0x4d224452801ACEd8B2F0aebE155379bb5D594381; + address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + constructor( + int24 _tickRangeMultiplier, + address _governance, + address _strategist, + address _controller, + address _timelock + ) + public + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) + { + tokenToNativeRoutes[ape] = abi.encodePacked(ape, uint24(3000), weth); + performanceTreasuryFee = 2000; + } + + function getName() external pure override returns (string memory) { + return "StrategyApeEthUniV3"; + } +} diff --git a/src/strategies/uniswapv3/strategy-univ3-eth-cow-lp.sol b/src/strategies/uniswapv3/strategy-univ3-eth-cow-lp.sol index 06b57f5de..d5f62be0c 100644 --- a/src/strategies/uniswapv3/strategy-univ3-eth-cow-lp.sol +++ b/src/strategies/uniswapv3/strategy-univ3-eth-cow-lp.sol @@ -1,31 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "./strategy-univ3-rebalance.sol"; contract StrategyEthCowUniV3 is StrategyRebalanceUniV3 { address private priv_pool = 0xFCfDFC98062d13a11cec48c44E4613eB26a34293; + address private cow = 0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB; + address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor( int24 _tickRangeMultiplier, - uint24 _swapPoolFee, address _governance, address _strategist, address _controller, address _timelock ) public - StrategyRebalanceUniV3( - priv_pool, - _tickRangeMultiplier, - _governance, - _strategist, - _controller, - _timelock - ) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { - swapPoolFee = (_swapPoolFee != 0) ? _swapPoolFee : pool.fee(); + tokenToNativeRoutes[cow] = abi.encodePacked(cow, uint24(10000), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/uniswapv3/strategy-univ3-eth-looks-lp.sol b/src/strategies/uniswapv3/strategy-univ3-eth-looks-lp.sol index a5f738161..0dde08e5f 100644 --- a/src/strategies/uniswapv3/strategy-univ3-eth-looks-lp.sol +++ b/src/strategies/uniswapv3/strategy-univ3-eth-looks-lp.sol @@ -1,31 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "./strategy-univ3-rebalance.sol"; contract StrategyEthLooksUniV3 is StrategyRebalanceUniV3 { address private priv_pool = 0x4b5Ab61593A2401B1075b90c04cBCDD3F87CE011; + address private looks = 0xf4d2888d29D722226FafA5d9B24F9164c092421E; + address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor( int24 _tickRangeMultiplier, - uint24 _swapPoolFee, address _governance, address _strategist, address _controller, address _timelock ) public - StrategyRebalanceUniV3( - priv_pool, - _tickRangeMultiplier, - _governance, - _strategist, - _controller, - _timelock - ) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { - swapPoolFee = (_swapPoolFee != 0) ? _swapPoolFee : pool.fee(); + tokenToNativeRoutes[looks] = abi.encodePacked(looks, uint24(3000), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/uniswapv3/strategy-univ3-eth-pickle-lp.sol b/src/strategies/uniswapv3/strategy-univ3-eth-pickle-lp.sol index 55dee8d73..45cd34101 100644 --- a/src/strategies/uniswapv3/strategy-univ3-eth-pickle-lp.sol +++ b/src/strategies/uniswapv3/strategy-univ3-eth-pickle-lp.sol @@ -1,31 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "./strategy-univ3-rebalance.sol"; contract StrategyEthPickleUniV3 is StrategyRebalanceUniV3 { address private priv_pool = 0x11c4D3b9cd07807F455371d56B3899bBaE662788; + address private pickle = 0x429881672B9AE42b8EbA0E26cD9C73711b891Ca5; + address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor( int24 _tickRangeMultiplier, - uint24 _swapPoolFee, address _governance, address _strategist, address _controller, address _timelock ) public - StrategyRebalanceUniV3( - priv_pool, - _tickRangeMultiplier, - _governance, - _strategist, - _controller, - _timelock - ) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { - swapPoolFee = (_swapPoolFee != 0) ? _swapPoolFee : pool.fee(); + tokenToNativeRoutes[pickle] = abi.encodePacked(pickle, uint24(10000), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/polygon/strategy-univ3-rebalance-staker.sol b/src/strategies/uniswapv3/strategy-univ3-rebalance-staker.sol similarity index 58% rename from src/strategies/polygon/strategy-univ3-rebalance-staker.sol rename to src/strategies/uniswapv3/strategy-univ3-rebalance-staker.sol index 0f974e694..933cc5ed5 100644 --- a/src/strategies/polygon/strategy-univ3-rebalance-staker.sol +++ b/src/strategies/uniswapv3/strategy-univ3-rebalance-staker.sol @@ -1,27 +1,31 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "../../lib/erc20.sol"; import "../../lib/safe-math.sol"; -import "../../polygon/lib/univ3/PoolActions.sol"; +import "../../lib/univ3/PoolActions.sol"; +import "../../lib/univ3/LiquidityAmounts.sol"; import "../../interfaces/uniswapv2.sol"; -import "../../polygon/interfaces/univ3/IUniswapV3PositionsNFT.sol"; -import "../../polygon/interfaces/univ3/IUniswapV3Pool.sol"; -import "../../polygon/interfaces//univ3/IUniswapV3Staker.sol"; -import "../../polygon/interfaces/univ3/ISwapRouter.sol"; +import "../../interfaces/univ3/IUniswapV3PositionsNFT.sol"; +import "../../interfaces/univ3/IUniswapV3Pool.sol"; +import "../../interfaces/univ3/IUniswapV3Staker.sol"; +import "../../interfaces/univ3/ISwapRouter02.sol"; import "../../interfaces/controllerv2.sol"; abstract contract StrategyRebalanceStakerUniV3 { using SafeERC20 for IERC20; using Address for address; using SafeMath for uint256; + using SafeMath for uint128; using PoolVariables for IUniswapV3Pool; // Perfomance fees - start with 20% uint256 public performanceTreasuryFee = 2000; uint256 public constant performanceTreasuryMax = 10000; + address public immutable native; + // User accounts address public governance; address public controller; @@ -31,8 +35,7 @@ abstract contract StrategyRebalanceStakerUniV3 { address public univ3_staker; // Dex - address public constant univ3Router = - 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; + address public constant univ3Router = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; // Tokens IUniswapV3Pool public pool; @@ -49,8 +52,9 @@ abstract contract StrategyRebalanceStakerUniV3 { uint24 private twapTime = 60; address public rewardToken; - IUniswapV3PositionsNFT public nftManager = - IUniswapV3PositionsNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + IUniswapV3PositionsNFT public nftManager = IUniswapV3PositionsNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + + mapping(address => bytes) public tokenToNativeRoutes; mapping(address => bool) public harvesters; @@ -58,15 +62,12 @@ abstract contract StrategyRebalanceStakerUniV3 { event InitialDeposited(uint256 tokenId); event Harvested(uint256 tokenId); - event Deposited( - uint256 tokenId, - uint256 token0Balance, - uint256 token1Balance - ); + event Deposited(uint256 tokenId, uint256 token0Balance, uint256 token1Balance); event Withdrawn(uint256 tokenId, uint256 _liquidity); event Rebalanced(uint256 tokenId, int24 _tickLower, int24 _tickUpper); constructor( + address _native, address _pool, int24 _tickRangeMultiplier, address _governance, @@ -74,6 +75,7 @@ abstract contract StrategyRebalanceStakerUniV3 { address _controller, address _timelock ) public { + native = _native; governance = _governance; strategist = _strategist; controller = _controller; @@ -88,19 +90,15 @@ abstract contract StrategyRebalanceStakerUniV3 { tickSpacing = pool.tickSpacing(); tickRangeMultiplier = _tickRangeMultiplier; - token0.safeApprove(address(nftManager), uint256(-1)); - token1.safeApprove(address(nftManager), uint256(-1)); + token0.safeApprove(address(nftManager), type(uint256).max); + token1.safeApprove(address(nftManager), type(uint256).max); nftManager.setApprovalForAll(univ3_staker, true); } // **** Modifiers **** // modifier onlyBenevolent() { - require( - harvesters[msg.sender] || - msg.sender == governance || - msg.sender == strategist - ); + require(harvesters[msg.sender] || msg.sender == governance || msg.sender == strategist); _; } @@ -119,9 +117,7 @@ abstract contract StrategyRebalanceStakerUniV3 { } function liquidityOfPool() public view returns (uint256) { - (, , , , , , , uint128 _liquidity, , , , ) = nftManager.positions( - tokenId - ); + (, , , , , , , uint128 _liquidity, , , , ) = nftManager.positions(tokenId); return _liquidity; } @@ -132,21 +128,13 @@ abstract contract StrategyRebalanceStakerUniV3 { function getName() external pure virtual returns (string memory); function isStakingActive() public view returns (bool stakingActive) { - return - (block.timestamp >= key.startTime && block.timestamp < key.endTime) - ? true - : false; + return (block.timestamp >= key.startTime && block.timestamp < key.endTime) ? true : false; } // **** Setters **** // function whitelistHarvesters(address[] calldata _harvesters) external { - require( - msg.sender == governance || - msg.sender == strategist || - harvesters[msg.sender], - "not authorized" - ); + require(msg.sender == governance || msg.sender == strategist || harvesters[msg.sender], "not authorized"); for (uint256 i = 0; i < _harvesters.length; i++) { harvesters[_harvesters[i]] = true; @@ -154,19 +142,14 @@ abstract contract StrategyRebalanceStakerUniV3 { } function revokeHarvesters(address[] calldata _harvesters) external { - require( - msg.sender == governance || msg.sender == strategist, - "not authorized" - ); + require(msg.sender == governance || msg.sender == strategist, "not authorized"); for (uint256 i = 0; i < _harvesters.length; i++) { harvesters[_harvesters[i]] = false; } } - function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) - external - { + function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) external { require(msg.sender == timelock, "!timelock"); performanceTreasuryFee = _performanceTreasuryFee; } @@ -222,12 +205,13 @@ abstract contract StrategyRebalanceStakerUniV3 { tickRangeMultiplier = _tickRangeMultiplier; } + function setTokenToNativeRoute(address token, bytes calldata path) external { + require(msg.sender == governance, "!governance"); + tokenToNativeRoutes[token] = path; + } + function amountsForLiquid() public view returns (uint256, uint256) { - (uint256 a1, uint256 a2) = pool.amountsForLiquidity( - 1e18, - tick_lower, - tick_upper - ); + (uint256 a1, uint256 a2) = pool.amountsForLiquidity(1e18, tick_lower, tick_upper); return (a1, a2); } @@ -236,15 +220,9 @@ abstract contract StrategyRebalanceStakerUniV3 { _observeTime[0] = twapTime; _observeTime[1] = 0; (int56[] memory _cumulativeTicks, ) = pool.observe(_observeTime); - int56 _averageTick = (_cumulativeTicks[1] - _cumulativeTicks[0]) / - twapTime; + int56 _averageTick = (_cumulativeTicks[1] - _cumulativeTicks[0]) / twapTime; int24 baseThreshold = tickSpacing * tickRangeMultiplier; - return - PoolVariables.baseTicks( - int24(_averageTick), - baseThreshold, - tickSpacing - ); + return PoolVariables.baseTicks(int24(_averageTick), baseThreshold, tickSpacing); } // **** State mutations **** // @@ -253,11 +231,7 @@ abstract contract StrategyRebalanceStakerUniV3 { // If NFT is held by staker, then withdraw if (nftManager.ownerOf(tokenId) != address(this) && isStakingActive()) { IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); + IUniswapV3Staker(univ3_staker).withdrawToken(tokenId, address(this), bytes("")); } uint256 _token0 = token0.balanceOf(address(this)); @@ -288,25 +262,61 @@ abstract contract StrategyRebalanceStakerUniV3 { } } - function _withdrawSome(uint256 _liquidity) - internal - returns (uint256, uint256) + ///@notice Takes tokens from sender, balances them, and deposits into the strategy position. To be used by the jar + function balanceAndDeposit(uint256 amount0Desired, uint256 amount1Desired) + external + returns ( + uint128 liquidity, + uint256 unusedAmount0, + uint256 unusedAmount1 + ) { + uint256 _balance0 = token0.balanceOf(address(this)); + uint256 _balance1 = token1.balanceOf(address(this)); + + token0.safeTransferFrom(msg.sender, address(this), amount0Desired); + token1.safeTransferFrom(msg.sender, address(this), amount1Desired); + + _balanceProportion(amount0Desired, amount1Desired); + + uint256 amount0DesiredBalanced = (token0.balanceOf(address(this))).sub(_balance0); + uint256 amount1DesiredBalanced = (token1.balanceOf(address(this))).sub(_balance1); + + (uint160 sqrtRatioX96, , , , , , ) = pool.slot0(); + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tick_lower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tick_upper); + + liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + amount0DesiredBalanced, + amount1DesiredBalanced + ); + (uint256 amount0Accepted, uint256 amount1Accepted) = LiquidityAmounts.getAmountsForLiquidity( + sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + liquidity + ); + + // Refund unused + unusedAmount0 = amount0DesiredBalanced.sub(amount0Accepted); + unusedAmount1 = amount1DesiredBalanced.sub(amount1Accepted); + token0.safeTransfer(msg.sender, unusedAmount0); + token1.safeTransfer(msg.sender, unusedAmount1); + + deposit(); + } + + function _withdrawSome(uint256 _liquidity) internal returns (uint256, uint256) { if (_liquidity == 0) return (0, 0); if (isStakingActive()) { IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); + IUniswapV3Staker(univ3_staker).withdrawToken(tokenId, address(this), bytes("")); } - (uint256 _a0Expect, uint256 _a1Expect) = pool.amountsForLiquidity( - uint128(_liquidity), - tick_lower, - tick_upper - ); + (uint256 _a0Expect, uint256 _a1Expect) = pool.amountsForLiquidity(uint128(_liquidity), tick_lower, tick_upper); (uint256 amount0, uint256 amount1) = nftManager.decreaseLiquidity( IUniswapV3PositionsNFT.DecreaseLiquidityParams({ tokenId: tokenId, @@ -338,10 +348,7 @@ abstract contract StrategyRebalanceStakerUniV3 { } // Override base withdraw function to redeposit - function withdraw(uint256 _liquidity) - external - returns (uint256 a0, uint256 a1) - { + function withdraw(uint256 _liquidity) external returns (uint256 a0, uint256 a1) { require(msg.sender == controller, "!controller"); (a0, a1) = _withdrawSome(_liquidity); @@ -379,16 +386,8 @@ abstract contract StrategyRebalanceStakerUniV3 { if (isStakingActive()) { IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - IUniswapV3Staker(univ3_staker).claimReward( - IERC20Minimal(rewardToken), - address(this), - 0 - ); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); + IUniswapV3Staker(univ3_staker).claimReward(IERC20Minimal(rewardToken), address(this), 0); + IUniswapV3Staker(univ3_staker).withdrawToken(tokenId, address(this), bytes("")); } nftManager.collect( @@ -408,7 +407,7 @@ abstract contract StrategyRebalanceStakerUniV3 { token1.balanceOf(address(this)).sub(_initToken1) ); - _balanceProportion(tick_lower, tick_upper); + _balanceProportion(token0.balanceOf(address(this)), token1.balanceOf(address(this))); deposit(); @@ -419,6 +418,7 @@ abstract contract StrategyRebalanceStakerUniV3 { //This assumes rewardToken == token0 function getHarvestable() public onlyBenevolent returns (uint256, uint256) { + //This will only update when someone mint/burn/pokes the pool. (uint256 _owed0, uint256 _owed1) = nftManager.collect( IUniswapV3PositionsNFT.CollectParams({ tokenId: tokenId, @@ -430,10 +430,7 @@ abstract contract StrategyRebalanceStakerUniV3 { uint256 _stakingRewards; if (isStakingActive()) { - _stakingRewards = IUniswapV3Staker(univ3_staker).rewards( - key.rewardToken, - address(this) - ); + _stakingRewards = IUniswapV3Staker(univ3_staker).rewards(key.rewardToken, address(this)); } if (address(key.rewardToken) == address(token0)) { _owed0 = _owed0 + uint128(_stakingRewards); @@ -451,16 +448,8 @@ abstract contract StrategyRebalanceStakerUniV3 { uint256 _liqAmt1 = token1.balanceOf(address(this)); // claim entire rewards IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); - IUniswapV3Staker(univ3_staker).claimReward( - IERC20Minimal(rewardToken), - address(this), - 0 - ); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); + IUniswapV3Staker(univ3_staker).claimReward(IERC20Minimal(rewardToken), address(this), 0); + IUniswapV3Staker(univ3_staker).withdrawToken(tokenId, address(this), bytes("")); _distributePerformanceFees( token0.balanceOf(address(this)).sub(_liqAmt0), @@ -479,20 +468,10 @@ abstract contract StrategyRebalanceStakerUniV3 { IUniswapV3Staker(univ3_staker).unstakeToken(key, tokenId); // claim entire rewards - IUniswapV3Staker(univ3_staker).claimReward( - IERC20Minimal(rewardToken), - address(this), - 0 - ); - IUniswapV3Staker(univ3_staker).withdrawToken( - tokenId, - address(this), - bytes("") - ); + IUniswapV3Staker(univ3_staker).claimReward(IERC20Minimal(rewardToken), address(this), 0); + IUniswapV3Staker(univ3_staker).withdrawToken(tokenId, address(this), bytes("")); } - (, , , , , , , uint256 _liquidity, , , , ) = nftManager.positions( - tokenId - ); + (, , , , , , , uint256 _liquidity, , , , ) = nftManager.positions(tokenId); (uint256 _liqAmt0, uint256 _liqAmt1) = nftManager.decreaseLiquidity( IUniswapV3PositionsNFT.DecreaseLiquidityParams({ tokenId: tokenId, @@ -523,19 +502,23 @@ abstract contract StrategyRebalanceStakerUniV3 { token1.balanceOf(address(this)).sub(_liqAmt1).sub(_initToken1) ); } - (int24 _tickLower, int24 _tickUpper) = determineTicks(); - _balanceProportion(_tickLower, _tickUpper); - //Need to do this again after the swap to cover any slippage. + + (tick_lower, tick_upper) = determineTicks(); uint256 _amount0Desired = token0.balanceOf(address(this)); uint256 _amount1Desired = token1.balanceOf(address(this)); + _balanceProportion(_amount0Desired, _amount1Desired); + + _amount0Desired = token0.balanceOf(address(this)); + _amount1Desired = token1.balanceOf(address(this)); + (_tokenId, , , ) = nftManager.mint( IUniswapV3PositionsNFT.MintParams({ token0: address(token0), token1: address(token1), - fee: pool.fee(), - tickLower: _tickLower, - tickUpper: _tickUpper, + fee: swapPoolFee, + tickLower: tick_lower, + tickUpper: tick_upper, amount0Desired: _amount0Desired, amount1Desired: _amount1Desired, amount0Min: 0, @@ -545,50 +528,42 @@ abstract contract StrategyRebalanceStakerUniV3 { }) ); - //Record updated information. + if (tokenId == 0) { + emit InitialDeposited(_tokenId); + } + + // Record updated tokenId tokenId = _tokenId; - tick_lower = _tickLower; - tick_upper = _tickUpper; + + // Balance and deposit dust, if any + _amount0Desired = token0.balanceOf(address(this)); + _amount1Desired = token1.balanceOf(address(this)); + if (_amount0Desired != 0 || _amount1Desired != 0) { + _balanceProportion(_amount0Desired, _amount1Desired); + deposit(); + } if (isStakingActive()) { nftManager.safeTransferFrom(address(this), univ3_staker, tokenId); IUniswapV3Staker(univ3_staker).stakeToken(key, tokenId); } - if (tokenId == 0) { - emit InitialDeposited(_tokenId); - } - - emit Rebalanced(tokenId, _tickLower, _tickUpper); + emit Rebalanced(tokenId, tick_lower, tick_upper); } // **** Emergency functions **** - function execute(address _target, bytes memory _data) - public - payable - returns (bytes memory response) - { + function execute(address _target, bytes memory _data) public payable returns (bytes memory response) { require(msg.sender == timelock, "!timelock"); require(_target != address(0), "!target"); // call contract in current context assembly { - let succeeded := delegatecall( - sub(gas(), 5000), - _target, - add(_data, 0x20), - mload(_data), - 0, - 0 - ) + let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 0) let size := returndatasize() response := mload(0x40) - mstore( - 0x40, - add(response, and(add(add(size, 0x20), 0x1f), not(0x1f))) - ) + mstore(0x40, add(response, and(add(add(size, 0x20), 0x1f), not(0x1f)))) mstore(response, size) returndatacopy(add(response, 0x20), 0, size) @@ -602,21 +577,40 @@ abstract contract StrategyRebalanceStakerUniV3 { // **** Internal functions **** - function _distributePerformanceFees(uint256 _amount0, uint256 _amount1) - internal - { - if (_amount0 > 0) { - IERC20(token0).safeTransfer( - IControllerV2(controller).treasury(), - _amount0.mul(performanceTreasuryFee).div(performanceTreasuryMax) - ); + function _distributePerformanceFees(uint256 _amount0, uint256 _amount1) internal { + uint256 _nativeToTreasury; + if (_amount0 != 0) { + uint256 _token0ToTrade = _amount0.mul(performanceTreasuryFee).div(performanceTreasuryMax); + if (_token0ToTrade != 0) { + if (tokenToNativeRoutes[address(token0)].length > 0) { + _nativeToTreasury += _swapUniV3WithPath( + address(token0), + tokenToNativeRoutes[address(token0)], + _token0ToTrade + ); + // token0 is native + } else { + _nativeToTreasury += _token0ToTrade; + } + } } - if (_amount1 > 0) { - IERC20(token1).safeTransfer( - IControllerV2(controller).treasury(), - _amount1.mul(performanceTreasuryFee).div(performanceTreasuryMax) - ); + if (_amount1 != 0) { + uint256 _token1ToTrade = _amount1.mul(performanceTreasuryFee).div(performanceTreasuryMax); + if (_token1ToTrade != 0) { + if (tokenToNativeRoutes[address(token1)].length > 0) { + _nativeToTreasury += _swapUniV3WithPath( + address(token1), + tokenToNativeRoutes[address(token1)], + _token1ToTrade + ); + // token1 is native + } else { + _nativeToTreasury += _token1ToTrade; + } + } } + if (_nativeToTreasury != 0) + IERC20(native).safeTransfer(IControllerV2(controller).treasury(), _nativeToTreasury); } function onERC721Received( @@ -628,66 +622,109 @@ abstract contract StrategyRebalanceStakerUniV3 { return this.onERC721Received.selector; } - function _balanceProportion(int24 _tickLower, int24 _tickUpper) internal { - PoolVariables.Info memory _cache; + ///@notice attempts to balance tokens to the optimal ratio for the current range + function _balanceProportion(uint256 amount0Desired, uint256 amount1Desired) internal { + uint256 amount0Accepted; + uint256 amount1Accepted; - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); + // Determining whether to trade + trade direction + (uint160 sqrtRatioX96, , , , , , ) = pool.slot0(); + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tick_lower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tick_upper); - //Get Max Liquidity for Amounts we own. - _cache.liquidity = pool.liquidityForAmounts( - _cache.amount0Desired, - _cache.amount1Desired, - _tickLower, - _tickUpper + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + uint128 liquidityForAmount0 = LiquidityAmounts.getLiquidityForAmount0( + sqrtRatioX96, + sqrtRatioBX96, + amount0Desired ); - - //Get correct amounts of each token for the liquidity we have. - (_cache.amount0, _cache.amount1) = pool.amountsForLiquidity( - _cache.liquidity, - _tickLower, - _tickUpper + uint128 liquidityForAmount1 = LiquidityAmounts.getLiquidityForAmount1( + sqrtRatioAX96, + sqrtRatioX96, + amount1Desired ); - //Determine Trade Direction - bool _zeroForOne; - if (_cache.amount1Desired == 0) { - _zeroForOne = true; - } else { - _zeroForOne = PoolVariables.amountsDirection( - _cache.amount0Desired, - _cache.amount1Desired, - _cache.amount0, - _cache.amount1 + int24 priceTick = TickMath.getTickAtSqrtRatio(sqrtRatioX96); + uint256 tickRange = uint256(tick_upper - tick_lower); + uint256 zeroRange = uint256(tick_upper - priceTick); + uint256 oneRange = uint256(priceTick - tick_lower); + + if (liquidityForAmount0 > liquidityForAmount1) { + // Excess is in token0 + (amount0Accepted, amount1Accepted) = LiquidityAmounts.getAmountsForLiquidity( + sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + liquidityForAmount1 ); - } - //Determine Amount to swap - uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0).div(2)) - : (_cache.amount1Desired.sub(_cache.amount1).div(2)); - - if (_amountSpecified > 0) { - //Determine Token to swap - address _inputToken = _zeroForOne - ? address(token0) - : address(token1); - - IERC20(_inputToken).safeApprove(univ3Router, 0); - IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); - - //Swap the token imbalanced - ISwapRouter(univ3Router).exactInputSingle( - ISwapRouter.ExactInputSingleParams({ - tokenIn: _inputToken, - tokenOut: _zeroForOne ? address(token1) : address(token0), + uint256 amountToBalance = amount0Desired - amount0Accepted; + uint256 amountToSwap = amountToBalance.sub(FullMath.mulDiv(amountToBalance, zeroRange, tickRange)); + + token0.safeApprove(univ3Router, 0); + token0.safeApprove(univ3Router, amountToSwap); + ISwapRouter02(univ3Router).exactInputSingle( + ISwapRouter02.ExactInputSingleParams({ + tokenIn: address(token0), + tokenOut: address(token1), + fee: swapPoolFee, + recipient: address(this), + amountIn: amountToSwap, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }) + ); + } else if (liquidityForAmount1 > liquidityForAmount0) { + // Excess is in token1 + (amount0Accepted, amount1Accepted) = LiquidityAmounts.getAmountsForLiquidity( + sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + liquidityForAmount0 + ); + + uint256 amountToBalance = amount1Desired - amount1Accepted; + uint256 amountToSwap = amountToBalance.sub(FullMath.mulDiv(amountToBalance, oneRange, tickRange)); + + token1.safeApprove(univ3Router, 0); + token1.safeApprove(univ3Router, amountToSwap); + ISwapRouter02(univ3Router).exactInputSingle( + ISwapRouter02.ExactInputSingleParams({ + tokenIn: address(token1), + tokenOut: address(token0), fee: swapPoolFee, recipient: address(this), - amountIn: _amountSpecified, + amountIn: amountToSwap, amountOutMinimum: 0, sqrtPriceLimitX96: 0 }) ); } } + + function _swapUniV3WithPath( + address _token, + bytes memory _path, + uint256 _amount + ) internal returns (uint256 _amountOut) { + _amountOut = 0; + if (_path.length > 0) { + IERC20(_token).safeApprove(univ3Router, 0); + IERC20(_token).safeApprove(univ3Router, _amount); + try + ISwapRouter02(univ3Router).exactInput( + ISwapRouter02.ExactInputParams({ + path: _path, + recipient: address(this), + amountIn: _amount, + amountOutMinimum: 0 + }) + ) + returns (uint256 _amountRecieved) { + _amountOut = _amountRecieved; + } catch { + // multi-hop swaps with too little amountIn can fail. Ignore. + } + } + } } diff --git a/src/strategies/uniswapv3/strategy-univ3-rebalance.sol b/src/strategies/uniswapv3/strategy-univ3-rebalance.sol index 5462ebca9..ff5c296f3 100644 --- a/src/strategies/uniswapv3/strategy-univ3-rebalance.sol +++ b/src/strategies/uniswapv3/strategy-univ3-rebalance.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "../../lib/erc20.sol"; import "../../lib/safe-math.sol"; import "../../lib/univ3/PoolActions.sol"; +import "../../lib/univ3/LiquidityAmounts.sol"; import "../../interfaces/uniswapv2.sol"; import "../../interfaces/univ3/IUniswapV3PositionsNFT.sol"; import "../../interfaces/univ3/IUniswapV3Pool.sol"; @@ -15,12 +16,15 @@ abstract contract StrategyRebalanceUniV3 { using SafeERC20 for IERC20; using Address for address; using SafeMath for uint256; + using SafeMath for uint128; using PoolVariables for IUniswapV3Pool; // Perfomance fees - start with 20% uint256 public performanceTreasuryFee = 2000; uint256 public constant performanceTreasuryMax = 10000; + address public immutable native; + // User accounts address public governance; address public controller; @@ -28,8 +32,7 @@ abstract contract StrategyRebalanceUniV3 { address public timelock; // Dex - address public constant univ3Router = - 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; + address public constant univ3Router = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; // Tokens IUniswapV3Pool public pool; @@ -45,22 +48,20 @@ abstract contract StrategyRebalanceUniV3 { uint24 public swapPoolFee; uint24 private twapTime = 60; - IUniswapV3PositionsNFT public nftManager = - IUniswapV3PositionsNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + IUniswapV3PositionsNFT public nftManager = IUniswapV3PositionsNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + + mapping(address => bytes) public tokenToNativeRoutes; mapping(address => bool) public harvesters; event InitialDeposited(uint256 tokenId); event Harvested(uint256 tokenId); - event Deposited( - uint256 tokenId, - uint256 token0Balance, - uint256 token1Balance - ); + event Deposited(uint256 tokenId, uint256 token0Balance, uint256 token1Balance); event Withdrawn(uint256 tokenId, uint256 _liquidity); event Rebalanced(uint256 tokenId, int24 _tickLower, int24 _tickUpper); constructor( + address _native, address _pool, int24 _tickRangeMultiplier, address _governance, @@ -68,6 +69,7 @@ abstract contract StrategyRebalanceUniV3 { address _controller, address _timelock ) public { + native = _native; governance = _governance; strategist = _strategist; controller = _controller; @@ -82,18 +84,14 @@ abstract contract StrategyRebalanceUniV3 { tickSpacing = pool.tickSpacing(); tickRangeMultiplier = _tickRangeMultiplier; - token0.safeApprove(address(nftManager), uint256(-1)); - token1.safeApprove(address(nftManager), uint256(-1)); + token0.safeApprove(address(nftManager), type(uint256).max); + token1.safeApprove(address(nftManager), type(uint256).max); } // **** Modifiers **** // modifier onlyBenevolent() { - require( - harvesters[msg.sender] || - msg.sender == governance || - msg.sender == strategist - ); + require(harvesters[msg.sender] || msg.sender == governance || msg.sender == strategist); _; } @@ -112,9 +110,7 @@ abstract contract StrategyRebalanceUniV3 { } function liquidityOfPool() public view returns (uint256) { - (, , , , , , , uint128 _liquidity, , , , ) = nftManager.positions( - tokenId - ); + (, , , , , , , uint128 _liquidity, , , , ) = nftManager.positions(tokenId); return _liquidity; } @@ -127,12 +123,7 @@ abstract contract StrategyRebalanceUniV3 { // **** Setters **** // function whitelistHarvesters(address[] calldata _harvesters) external { - require( - msg.sender == governance || - msg.sender == strategist || - harvesters[msg.sender], - "not authorized" - ); + require(msg.sender == governance || msg.sender == strategist || harvesters[msg.sender], "not authorized"); for (uint256 i = 0; i < _harvesters.length; i++) { harvesters[_harvesters[i]] = true; @@ -140,19 +131,14 @@ abstract contract StrategyRebalanceUniV3 { } function revokeHarvesters(address[] calldata _harvesters) external { - require( - msg.sender == governance || msg.sender == strategist, - "not authorized" - ); + require(msg.sender == governance || msg.sender == strategist, "not authorized"); for (uint256 i = 0; i < _harvesters.length; i++) { harvesters[_harvesters[i]] = false; } } - function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) - external - { + function setPerformanceTreasuryFee(uint256 _performanceTreasuryFee) external { require(msg.sender == timelock, "!timelock"); performanceTreasuryFee = _performanceTreasuryFee; } @@ -192,12 +178,13 @@ abstract contract StrategyRebalanceUniV3 { tickRangeMultiplier = _tickRangeMultiplier; } + function setTokenToNativeRoute(address token, bytes calldata path) external { + require(msg.sender == governance, "!governance"); + tokenToNativeRoutes[token] = path; + } + function amountsForLiquid() public view returns (uint256, uint256) { - (uint256 a1, uint256 a2) = pool.amountsForLiquidity( - 1e18, - tick_lower, - tick_upper - ); + (uint256 a1, uint256 a2) = pool.amountsForLiquidity(1e18, tick_lower, tick_upper); return (a1, a2); } @@ -206,15 +193,9 @@ abstract contract StrategyRebalanceUniV3 { _observeTime[0] = twapTime; _observeTime[1] = 0; (int56[] memory _cumulativeTicks, ) = pool.observe(_observeTime); - int56 _averageTick = (_cumulativeTicks[1] - _cumulativeTicks[0]) / - twapTime; + int56 _averageTick = (_cumulativeTicks[1] - _cumulativeTicks[0]) / twapTime; int24 baseThreshold = tickSpacing * tickRangeMultiplier; - return - PoolVariables.baseTicks( - int24(_averageTick), - baseThreshold, - tickSpacing - ); + return PoolVariables.baseTicks(int24(_averageTick), baseThreshold, tickSpacing); } // **** State mutations **** // @@ -239,17 +220,57 @@ abstract contract StrategyRebalanceUniV3 { emit Deposited(tokenId, _token0, _token1); } - function _withdrawSome(uint256 _liquidity) - internal - returns (uint256, uint256) + ///@notice Takes tokens from sender, balances them, and deposits into the strategy position. To be used by the jar + function balanceAndDeposit(uint256 amount0Desired, uint256 amount1Desired) + external + returns ( + uint128 liquidity, + uint256 unusedAmount0, + uint256 unusedAmount1 + ) { - if (_liquidity == 0) return (0, 0); + uint256 _balance0 = token0.balanceOf(address(this)); + uint256 _balance1 = token1.balanceOf(address(this)); + + token0.safeTransferFrom(msg.sender, address(this), amount0Desired); + token1.safeTransferFrom(msg.sender, address(this), amount1Desired); - (uint256 _a0Expect, uint256 _a1Expect) = pool.amountsForLiquidity( - uint128(_liquidity), - tick_lower, - tick_upper + _balanceProportion(amount0Desired, amount1Desired); + + uint256 amount0DesiredBalanced = (token0.balanceOf(address(this))).sub(_balance0); + uint256 amount1DesiredBalanced = (token1.balanceOf(address(this))).sub(_balance1); + + (uint160 sqrtRatioX96, , , , , , ) = pool.slot0(); + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tick_lower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tick_upper); + + liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + amount0DesiredBalanced, + amount1DesiredBalanced ); + (uint256 amount0Accepted, uint256 amount1Accepted) = LiquidityAmounts.getAmountsForLiquidity( + sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + liquidity + ); + + // Refund unused + unusedAmount0 = amount0DesiredBalanced.sub(amount0Accepted); + unusedAmount1 = amount1DesiredBalanced.sub(amount1Accepted); + token0.safeTransfer(msg.sender, unusedAmount0); + token1.safeTransfer(msg.sender, unusedAmount1); + + deposit(); + } + + function _withdrawSome(uint256 _liquidity) internal returns (uint256, uint256) { + if (_liquidity == 0) return (0, 0); + + (uint256 _a0Expect, uint256 _a1Expect) = pool.amountsForLiquidity(uint128(_liquidity), tick_lower, tick_upper); (uint256 amount0, uint256 amount1) = nftManager.decreaseLiquidity( IUniswapV3PositionsNFT.DecreaseLiquidityParams({ tokenId: tokenId, @@ -280,10 +301,7 @@ abstract contract StrategyRebalanceUniV3 { _asset.safeTransfer(controller, balance); } - function withdraw(uint256 _liquidity) - external - returns (uint256 a0, uint256 a1) - { + function withdraw(uint256 _liquidity) external returns (uint256 a0, uint256 a1) { require(msg.sender == controller, "!controller"); (a0, a1) = _withdrawSome(_liquidity); @@ -333,7 +351,7 @@ abstract contract StrategyRebalanceUniV3 { token1.balanceOf(address(this)).sub(_initToken1) ); - _balanceProportion(tick_lower, tick_upper); + _balanceProportion(token0.balanceOf(address(this)), token1.balanceOf(address(this))); deposit(); @@ -357,9 +375,7 @@ abstract contract StrategyRebalanceUniV3 { if (tokenId != 0) { uint256 _initToken0 = token0.balanceOf(address(this)); uint256 _initToken1 = token1.balanceOf(address(this)); - (, , , , , , , uint256 _liquidity, , , , ) = nftManager.positions( - tokenId - ); + (, , , , , , , uint256 _liquidity, , , , ) = nftManager.positions(tokenId); (uint256 _liqAmt0, uint256 _liqAmt1) = nftManager.decreaseLiquidity( IUniswapV3PositionsNFT.DecreaseLiquidityParams({ tokenId: tokenId, @@ -391,19 +407,22 @@ abstract contract StrategyRebalanceUniV3 { ); } - (int24 _tickLower, int24 _tickUpper) = determineTicks(); - _balanceProportion(_tickLower, _tickUpper); - //Need to do this again after the swap to cover any slippage. + (tick_lower, tick_upper) = determineTicks(); uint256 _amount0Desired = token0.balanceOf(address(this)); uint256 _amount1Desired = token1.balanceOf(address(this)); + _balanceProportion(_amount0Desired, _amount1Desired); + + _amount0Desired = token0.balanceOf(address(this)); + _amount1Desired = token1.balanceOf(address(this)); + (_tokenId, , , ) = nftManager.mint( IUniswapV3PositionsNFT.MintParams({ token0: address(token0), token1: address(token1), - fee: pool.fee(), - tickLower: _tickLower, - tickUpper: _tickUpper, + fee: swapPoolFee, + tickLower: tick_lower, + tickUpper: tick_upper, amount0Desired: _amount0Desired, amount1Desired: _amount1Desired, amount0Min: 0, @@ -413,45 +432,37 @@ abstract contract StrategyRebalanceUniV3 { }) ); - //Record updated information. - tokenId = _tokenId; - tick_lower = _tickLower; - tick_upper = _tickUpper; - if (tokenId == 0) { emit InitialDeposited(_tokenId); } - emit Rebalanced(tokenId, _tickLower, _tickUpper); + // Record updated tokenId + tokenId = _tokenId; + + // Balance and deposit dust, if any + _amount0Desired = token0.balanceOf(address(this)); + _amount1Desired = token1.balanceOf(address(this)); + if (_amount0Desired != 0 || _amount1Desired != 0) { + _balanceProportion(_amount0Desired, _amount1Desired); + deposit(); + } + + emit Rebalanced(tokenId, tick_lower, tick_upper); } // **** Emergency functions **** - function execute(address _target, bytes memory _data) - public - payable - returns (bytes memory response) - { + function execute(address _target, bytes memory _data) public payable returns (bytes memory response) { require(msg.sender == timelock, "!timelock"); require(_target != address(0), "!target"); // call contract in current context assembly { - let succeeded := delegatecall( - sub(gas(), 5000), - _target, - add(_data, 0x20), - mload(_data), - 0, - 0 - ) + let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 0) let size := returndatasize() response := mload(0x40) - mstore( - 0x40, - add(response, and(add(add(size, 0x20), 0x1f), not(0x1f))) - ) + mstore(0x40, add(response, and(add(add(size, 0x20), 0x1f), not(0x1f)))) mstore(response, size) returndatacopy(add(response, 0x20), 0, size) @@ -464,62 +475,125 @@ abstract contract StrategyRebalanceUniV3 { } // **** Internal functions **** - function _balanceProportion(int24 _tickLower, int24 _tickUpper) internal { - PoolVariables.Info memory _cache; - - _cache.amount0Desired = token0.balanceOf(address(this)); - _cache.amount1Desired = token1.balanceOf(address(this)); - - //Get Max Liquidity for Amounts we own. - _cache.liquidity = pool.liquidityForAmounts( - _cache.amount0Desired, - _cache.amount1Desired, - _tickLower, - _tickUpper - ); - //Get correct amounts of each token for the liquidity we have. - (_cache.amount0, _cache.amount1) = pool.amountsForLiquidity( - _cache.liquidity, - _tickLower, - _tickUpper + function _distributePerformanceFees(uint256 _amount0, uint256 _amount1) internal { + uint256 _nativeToTreasury; + if (_amount0 != 0) { + uint256 _token0ToTrade = _amount0.mul(performanceTreasuryFee).div(performanceTreasuryMax); + if (_token0ToTrade != 0) { + if (tokenToNativeRoutes[address(token0)].length > 0) { + _nativeToTreasury += _swapUniV3WithPath( + address(token0), + tokenToNativeRoutes[address(token0)], + _token0ToTrade + ); + // token0 is native + } else { + _nativeToTreasury += _token0ToTrade; + } + } + } + if (_amount1 != 0) { + uint256 _token1ToTrade = _amount1.mul(performanceTreasuryFee).div(performanceTreasuryMax); + if (_token1ToTrade != 0) { + if (tokenToNativeRoutes[address(token1)].length > 0) { + _nativeToTreasury += _swapUniV3WithPath( + address(token1), + tokenToNativeRoutes[address(token1)], + _token1ToTrade + ); + // token1 is native + } else { + _nativeToTreasury += _token1ToTrade; + } + } + } + if (_nativeToTreasury != 0) + IERC20(native).safeTransfer(IControllerV2(controller).treasury(), _nativeToTreasury); + } + + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public pure returns (bytes4) { + return this.onERC721Received.selector; + } + + ///@notice attempts to balance tokens to the optimal ratio for the current range + function _balanceProportion(uint256 amount0Desired, uint256 amount1Desired) internal { + uint256 amount0Accepted; + uint256 amount1Accepted; + + // Determining whether to trade + trade direction + (uint160 sqrtRatioX96, , , , , , ) = pool.slot0(); + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tick_lower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tick_upper); + + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + uint128 liquidityForAmount0 = LiquidityAmounts.getLiquidityForAmount0( + sqrtRatioX96, + sqrtRatioBX96, + amount0Desired + ); + uint128 liquidityForAmount1 = LiquidityAmounts.getLiquidityForAmount1( + sqrtRatioAX96, + sqrtRatioX96, + amount1Desired ); - //Determine Trade Direction - bool _zeroForOne; - if (_cache.amount1Desired == 0) { - _zeroForOne = true; - } else { - _zeroForOne = PoolVariables.amountsDirection( - _cache.amount0Desired, - _cache.amount1Desired, - _cache.amount0, - _cache.amount1 + int24 priceTick = TickMath.getTickAtSqrtRatio(sqrtRatioX96); + uint256 tickRange = uint256(tick_upper - tick_lower); + uint256 zeroRange = uint256(tick_upper - priceTick); + uint256 oneRange = uint256(priceTick - tick_lower); + + if (liquidityForAmount0 > liquidityForAmount1) { + // Excess is in token0 + (amount0Accepted, amount1Accepted) = LiquidityAmounts.getAmountsForLiquidity( + sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + liquidityForAmount1 ); - } - //Determine Amount to swap - uint256 _amountSpecified = _zeroForOne - ? (_cache.amount0Desired.sub(_cache.amount0).div(2)) - : (_cache.amount1Desired.sub(_cache.amount1).div(2)); + uint256 amountToBalance = amount0Desired - amount0Accepted; + uint256 amountToSwap = amountToBalance.sub(FullMath.mulDiv(amountToBalance, zeroRange, tickRange)); - if (_amountSpecified > 0) { - //Determine Token to swap - address _inputToken = _zeroForOne - ? address(token0) - : address(token1); + token0.safeApprove(univ3Router, 0); + token0.safeApprove(univ3Router, amountToSwap); + ISwapRouter02(univ3Router).exactInputSingle( + ISwapRouter02.ExactInputSingleParams({ + tokenIn: address(token0), + tokenOut: address(token1), + fee: swapPoolFee, + recipient: address(this), + amountIn: amountToSwap, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }) + ); + } else if (liquidityForAmount1 > liquidityForAmount0) { + // Excess is in token1 + (amount0Accepted, amount1Accepted) = LiquidityAmounts.getAmountsForLiquidity( + sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + liquidityForAmount0 + ); - IERC20(_inputToken).safeApprove(univ3Router, 0); - IERC20(_inputToken).safeApprove(univ3Router, _amountSpecified); + uint256 amountToBalance = amount1Desired - amount1Accepted; + uint256 amountToSwap = amountToBalance.sub(FullMath.mulDiv(amountToBalance, oneRange, tickRange)); - //Swap the token imbalanced + token1.safeApprove(univ3Router, 0); + token1.safeApprove(univ3Router, amountToSwap); ISwapRouter02(univ3Router).exactInputSingle( ISwapRouter02.ExactInputSingleParams({ - tokenIn: _inputToken, - tokenOut: _zeroForOne ? address(token1) : address(token0), + tokenIn: address(token1), + tokenOut: address(token0), fee: swapPoolFee, recipient: address(this), - amountIn: _amountSpecified, + amountIn: amountToSwap, amountOutMinimum: 0, sqrtPriceLimitX96: 0 }) @@ -527,29 +601,29 @@ abstract contract StrategyRebalanceUniV3 { } } - function _distributePerformanceFees(uint256 _amount0, uint256 _amount1) - internal - { - if (_amount0 > 0) { - IERC20(token0).safeTransfer( - IControllerV2(controller).treasury(), - _amount0.mul(performanceTreasuryFee).div(performanceTreasuryMax) - ); - } - if (_amount1 > 0) { - IERC20(token1).safeTransfer( - IControllerV2(controller).treasury(), - _amount1.mul(performanceTreasuryFee).div(performanceTreasuryMax) - ); + function _swapUniV3WithPath( + address _token, + bytes memory _path, + uint256 _amount + ) internal returns (uint256 _amountOut) { + _amountOut = 0; + if (_path.length > 0) { + IERC20(_token).safeApprove(univ3Router, 0); + IERC20(_token).safeApprove(univ3Router, _amount); + try + ISwapRouter02(univ3Router).exactInput( + ISwapRouter02.ExactInputParams({ + path: _path, + recipient: address(this), + amountIn: _amount, + amountOutMinimum: 0 + }) + ) + returns (uint256 _amountRecieved) { + _amountOut = _amountRecieved; + } catch { + // multi-hop swaps with too little amountIn can fail. Ignore. + } } } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC721Received.selector; - } } diff --git a/src/strategies/uniswapv3/strategy-univ3-usdc-eth-05-lp.sol b/src/strategies/uniswapv3/strategy-univ3-usdc-eth-05-lp.sol index 3f2c6324c..90863362b 100644 --- a/src/strategies/uniswapv3/strategy-univ3-usdc-eth-05-lp.sol +++ b/src/strategies/uniswapv3/strategy-univ3-usdc-eth-05-lp.sol @@ -1,31 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "./strategy-univ3-rebalance.sol"; contract StrategyUsdcEth05UniV3 is StrategyRebalanceUniV3 { address private priv_pool = 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640; + address private usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor( int24 _tickRangeMultiplier, - uint24 _swapPoolFee, address _governance, address _strategist, address _controller, address _timelock ) public - StrategyRebalanceUniV3( - priv_pool, - _tickRangeMultiplier, - _governance, - _strategist, - _controller, - _timelock - ) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { - swapPoolFee = (_swapPoolFee != 0) ? _swapPoolFee : pool.fee(); + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(500), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/uniswapv3/strategy-univ3-usdc-eth-3-lp.sol b/src/strategies/uniswapv3/strategy-univ3-usdc-eth-3-lp.sol index b25a50abb..56634b76a 100644 --- a/src/strategies/uniswapv3/strategy-univ3-usdc-eth-3-lp.sol +++ b/src/strategies/uniswapv3/strategy-univ3-usdc-eth-3-lp.sol @@ -1,31 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "./strategy-univ3-rebalance.sol"; contract StrategyUsdcEth3UniV3 is StrategyRebalanceUniV3 { address private priv_pool = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; + address private usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor( int24 _tickRangeMultiplier, - uint24 _swapPoolFee, address _governance, address _strategist, address _controller, address _timelock ) public - StrategyRebalanceUniV3( - priv_pool, - _tickRangeMultiplier, - _governance, - _strategist, - _controller, - _timelock - ) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { - swapPoolFee = (_swapPoolFee != 0) ? _swapPoolFee : pool.fee(); + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(3000), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/uniswapv3/strategy-univ3-usdc-usdt-lp.sol b/src/strategies/uniswapv3/strategy-univ3-usdc-usdt-lp.sol index 013337d11..e4b074e08 100644 --- a/src/strategies/uniswapv3/strategy-univ3-usdc-usdt-lp.sol +++ b/src/strategies/uniswapv3/strategy-univ3-usdc-usdt-lp.sol @@ -1,31 +1,28 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "./strategy-univ3-rebalance.sol"; contract StrategyUsdcUsdtUniV3 is StrategyRebalanceUniV3 { address private priv_pool = 0x3416cF6C708Da44DB2624D63ea0AAef7113527C6; + address private usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address private usdt = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor( int24 _tickRangeMultiplier, - uint24 _swapPoolFee, address _governance, address _strategist, address _controller, address _timelock ) public - StrategyRebalanceUniV3( - priv_pool, - _tickRangeMultiplier, - _governance, - _strategist, - _controller, - _timelock - ) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { - swapPoolFee = (_swapPoolFee != 0) ? _swapPoolFee : pool.fee(); + tokenToNativeRoutes[usdc] = abi.encodePacked(usdc, uint24(500), weth); + tokenToNativeRoutes[usdt] = abi.encodePacked(usdt, uint24(100), usdc, uint24(500), weth); + performanceTreasuryFee = 1000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/uniswapv3/strategy-univ3-wbtc-eth-lp.sol b/src/strategies/uniswapv3/strategy-univ3-wbtc-eth-lp.sol index 27fdd6ba9..faa450559 100644 --- a/src/strategies/uniswapv3/strategy-univ3-wbtc-eth-lp.sol +++ b/src/strategies/uniswapv3/strategy-univ3-wbtc-eth-lp.sol @@ -1,31 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.12; +pragma solidity >=0.6.12 <0.8.0; pragma experimental ABIEncoderV2; import "./strategy-univ3-rebalance.sol"; contract StrategyWbtcEthUniV3 is StrategyRebalanceUniV3 { address private priv_pool = 0x4585FE77225b41b697C938B018E2Ac67Ac5a20c0; + address private wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; + address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor( int24 _tickRangeMultiplier, - uint24 _swapPoolFee, address _governance, address _strategist, address _controller, address _timelock ) public - StrategyRebalanceUniV3( - priv_pool, - _tickRangeMultiplier, - _governance, - _strategist, - _controller, - _timelock - ) + StrategyRebalanceUniV3(weth, priv_pool, _tickRangeMultiplier, _governance, _strategist, _controller, _timelock) { - swapPoolFee = (_swapPoolFee != 0) ? _swapPoolFee : pool.fee(); + tokenToNativeRoutes[wbtc] = abi.encodePacked(wbtc, uint24(500), weth); + performanceTreasuryFee = 2000; } function getName() external pure override returns (string memory) { diff --git a/src/strategies/uwu/strategy-uwu-base.sol b/src/strategies/uwu/strategy-uwu-base.sol index 0e359dee9..d406cdcc1 100644 --- a/src/strategies/uwu/strategy-uwu-base.sol +++ b/src/strategies/uwu/strategy-uwu-base.sol @@ -5,6 +5,7 @@ import "../../interfaces/uwu/uwu-lend.sol"; import "../../interfaces/uwu/data-provider.sol"; import "../../interfaces/uwu/uwu-rewards.sol"; import "../../interfaces/univ3/ISwapRouter.sol"; +import "../../interfaces/uniswapv2.sol"; import {DataTypes} from "../../interfaces/uwu/data-types.sol"; import "../strategy-base-v2.sol"; @@ -16,6 +17,7 @@ abstract contract StrategyUwuBase is StrategyBase { address public constant uwu = 0x55C08ca52497e2f1534B59E2917BF524D4765257; address public constant lendingPool = 0x2409aF0251DCB89EE3Dee572629291f9B087c668; address public constant dataProviderAddr = 0x17938eDE656Ca1901807abf43a6B1D138D8Cd521; + address private constant _weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public immutable aToken; address public immutable variableDebtToken; @@ -33,6 +35,9 @@ abstract contract StrategyUwuBase is StrategyBase { uint256 colFactorLeverageBuffer = 40; uint256 colFactorLeverageBufferMax = 1000; + address public constant sushiRouter = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F; + address public constant univ3Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564; + constructor( address _token, bytes memory _path, @@ -40,7 +45,7 @@ abstract contract StrategyUwuBase is StrategyBase { address _strategist, address _controller, address _timelock - ) StrategyBase(_token, _governance, _strategist, _controller, _timelock) { + ) StrategyBase(_token, _weth, _governance, _strategist, _controller, _timelock) { nativeToTokenPath = _path; DataTypes.ReserveData memory reserveData = IUwuLend(lendingPool).getReserveData(_token); @@ -48,7 +53,7 @@ abstract contract StrategyUwuBase is StrategyBase { variableDebtToken = reserveData.variableDebtTokenAddress; dataProvider = IDataProvider(dataProviderAddr); - IERC20(weth).approve(univ3Router, type(uint256).max); + IERC20(native).approve(univ3Router, type(uint256).max); } // **** Modifiers **** // @@ -110,12 +115,18 @@ abstract contract StrategyUwuBase is StrategyBase { return leverage; } - function getHarvestable() external view override returns (uint256) { + function getHarvestable() external view override returns (address[] memory, uint256[] memory) { address[] memory aTokens = new address[](1); aTokens[0] = aToken; uint256[] memory rewards = uwuRewards.claimableReward(address(this), aTokens); - return rewards[0].div(2); + address[] memory rewardTokens = new address[](1); + rewardTokens[0] = uwu; + + uint256[] memory pendingRewards = new uint256[](1); + pendingRewards[0] = rewards[0].div(2); + + return (rewardTokens, pendingRewards); } function getColFactor() public view returns (uint256) { @@ -281,7 +292,7 @@ abstract contract StrategyUwuBase is StrategyBase { } } - function harvest() public override onlyBenevolent { + function harvest() public override onlyHarvester { address[] memory aTokens = new address[](1); aTokens[0] = aToken; @@ -295,13 +306,13 @@ abstract contract StrategyUwuBase is StrategyBase { IERC20(uwu).safeApprove(sushiRouter, _uwu); address[] memory path = new address[](2); path[0] = uwu; - path[1] = weth; + path[1] = native; _swapWithPath(sushiRouter, path, _uwu); _distributePerformanceFeesNative(); - uint256 _weth = IERC20(weth).balanceOf(address(this)); + uint256 _native = IERC20(native).balanceOf(address(this)); if (nativeToTokenPath.length > 0) ISwapRouter(univ3Router).exactInput( @@ -309,7 +320,7 @@ abstract contract StrategyUwuBase is StrategyBase { path: nativeToTokenPath, recipient: address(this), deadline: block.timestamp + 300, - amountIn: _weth, + amountIn: _native, amountOutMinimum: 0 }) ); @@ -354,4 +365,17 @@ abstract contract StrategyUwuBase is StrategyBase { return _amount; } + + function _swapWithPath( + address router, + address[] memory path, + uint256 _amount + ) internal { + require(path[1] != address(0)); + UniswapRouterV2(router).swapExactTokensForTokens(_amount, 0, path, address(this), block.timestamp.add(60)); + } + + function _addToNativeRoute(bytes memory path) internal override {} + + function _addToTokenRoute(bytes memory) internal override {} } diff --git a/src/strategies/uwu/strategy-uwu-dai.sol b/src/strategies/uwu/strategy-uwu-dai.sol index badeab708..1341ca3e2 100644 --- a/src/strategies/uwu/strategy-uwu-dai.sol +++ b/src/strategies/uwu/strategy-uwu-dai.sol @@ -6,6 +6,7 @@ import "./strategy-uwu-base.sol"; contract StrategyUwuDai is StrategyUwuBase { // Token addresses address private constant dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; bytes path = abi.encodePacked(weth, uint24(500), dai); diff --git a/src/strategies/uwu/strategy-uwu-frax.sol b/src/strategies/uwu/strategy-uwu-frax.sol index 8518c8e5e..1b5b14ff3 100644 --- a/src/strategies/uwu/strategy-uwu-frax.sol +++ b/src/strategies/uwu/strategy-uwu-frax.sol @@ -7,6 +7,7 @@ contract StrategyUwuFrax is StrategyUwuBase { // Token addresses address private constant frax = 0x853d955aCEf822Db058eb8505911ED77F175b99e; address private constant usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address private constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; bytes path = abi.encodePacked(weth, uint24(500), usdc, uint24(500), frax); diff --git a/src/strategies/uwu/strategy-uwu-wbtc.sol b/src/strategies/uwu/strategy-uwu-wbtc.sol index 5fee2c3a9..e5e86e942 100644 --- a/src/strategies/uwu/strategy-uwu-wbtc.sol +++ b/src/strategies/uwu/strategy-uwu-wbtc.sol @@ -6,6 +6,7 @@ import "./strategy-uwu-base.sol"; contract StrategyUwuWbtc is StrategyUwuBase { // Token addresses address private constant wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; + address private constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; bytes path = abi.encodePacked(weth, uint24(500), wbtc); constructor( diff --git a/src/strategies/uwu/strategy-uwu-weth.sol b/src/strategies/uwu/strategy-uwu-weth.sol index 5c6e2db12..4b5386053 100644 --- a/src/strategies/uwu/strategy-uwu-weth.sol +++ b/src/strategies/uwu/strategy-uwu-weth.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.6; import "./strategy-uwu-base.sol"; contract StrategyUwuWeth is StrategyUwuBase { + address private constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; bytes path = ""; constructor( diff --git a/src/tests/jar-converters/curve-curve.test.sol b/src/tests/jar-converters/curve-curve.test.sol deleted file mode 100644 index e3f7b720d..000000000 --- a/src/tests/jar-converters/curve-curve.test.sol +++ /dev/null @@ -1,564 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; -pragma experimental ABIEncoderV2; - -import "../lib/hevm.sol"; -import "../lib/user.sol"; -import "../lib/test-approx.sol"; -import "../lib/test-defi-base.sol"; - -import "../../interfaces/strategy.sol"; -import "../../interfaces/curve.sol"; -import "../../interfaces/uniswapv2.sol"; - -import "../../pickle-jar.sol"; -import "../../controller-v4.sol"; - -import "../../proxy-logic/curve.sol"; -import "../../proxy-logic/uniswapv2.sol"; - -import "../../strategies/curve/strategy-curve-scrv-v3_2.sol"; -import "../../strategies/curve/strategy-curve-rencrv-v2.sol"; -import "../../strategies/curve/strategy-curve-3crv-v2.sol"; - -contract StrategyCurveCurveJarSwapTest is DSTestDefiBase { - address governance; - address strategist; - address devfund; - address treasury; - address timelock; - - IStrategy[] curveStrategies; - - PickleJar[] curvePickleJars; - - ControllerV4 controller; - - CurveProxyLogic curveProxyLogic; - UniswapV2ProxyLogic uniswapV2ProxyLogic; - - address[] curvePools; - address[] curveLps; - - function setUp() public { - governance = address(this); - strategist = address(this); - devfund = address(new User()); - treasury = address(new User()); - timelock = address(this); - - controller = new ControllerV4( - governance, - strategist, - timelock, - devfund, - treasury - ); - - // Curve Strategies - curveStrategies = new IStrategy[](3); - curvePickleJars = new PickleJar[](curveStrategies.length); - curveLps = new address[](curveStrategies.length); - curvePools = new address[](curveStrategies.length); - - curveLps[0] = three_crv; - curvePools[0] = three_pool; - curveStrategies[0] = IStrategy( - address( - new StrategyCurve3CRVv2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - curveLps[1] = scrv; - curvePools[1] = susdv2_pool; - curveStrategies[1] = IStrategy( - address( - new StrategyCurveSCRVv3_2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - curveLps[2] = ren_crv; - curvePools[2] = ren_pool; - curveStrategies[2] = IStrategy( - address( - new StrategyCurveRenCRVv2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - // Create PICKLE Jars - for (uint256 i = 0; i < curvePickleJars.length; i++) { - curvePickleJars[i] = new PickleJar( - curveStrategies[i].want(), - governance, - timelock, - address(controller) - ); - - controller.setJar( - curveStrategies[i].want(), - address(curvePickleJars[i]) - ); - controller.approveStrategy( - curveStrategies[i].want(), - address(curveStrategies[i]) - ); - controller.setStrategy( - curveStrategies[i].want(), - address(curveStrategies[i]) - ); - } - - curveProxyLogic = new CurveProxyLogic(); - uniswapV2ProxyLogic = new UniswapV2ProxyLogic(); - - controller.approveJarConverter(address(curveProxyLogic)); - controller.approveJarConverter(address(uniswapV2ProxyLogic)); - - hevm.warp(startTime); - } - - function _getCurveLP(address curve, uint256 amount) internal { - if (curve == ren_pool) { - _getERC20(wbtc, amount); - uint256 _wbtc = IERC20(wbtc).balanceOf(address(this)); - IERC20(wbtc).approve(curve, _wbtc); - - uint256[2] memory liquidity; - liquidity[1] = _wbtc; - ICurveFi_2(curve).add_liquidity(liquidity, 0); - } else { - _getERC20(dai, amount); - uint256 _dai = IERC20(dai).balanceOf(address(this)); - IERC20(dai).approve(curve, _dai); - - if (curve == three_pool) { - uint256[3] memory liquidity; - liquidity[0] = _dai; - ICurveFi_3(curve).add_liquidity(liquidity, 0); - } else { - uint256[4] memory liquidity; - liquidity[0] = _dai; - ICurveFi_4(curve).add_liquidity(liquidity, 0); - } - } - } - - // **** Internal functions **** // - // Theres so many internal functions due to stack blowing up - - // Some post swap checks - // Checks if there's any leftover funds in the converter contract - function _post_swap_check(uint256 fromIndex, uint256 toIndex) internal { - IERC20 token0 = curvePickleJars[fromIndex].token(); - IERC20 token1 = curvePickleJars[toIndex].token(); - - uint256 MAX_DUST = 10; - - // No funds left behind - assertEq(curvePickleJars[fromIndex].balanceOf(address(controller)), 0); - assertEq(curvePickleJars[toIndex].balanceOf(address(controller)), 0); - assertTrue(token0.balanceOf(address(controller)) < MAX_DUST); - assertTrue(token1.balanceOf(address(controller)) < MAX_DUST); - - // Make sure only controller can call 'withdrawForSwap' - try curveStrategies[fromIndex].withdrawForSwap(0) { - revert("!withdraw-for-swap-only-controller"); - } catch {} - } - - function _test_check_treasury_fee(uint256 _amount, uint256 earned) - internal - { - assertEqApprox( - _amount.mul(controller.convenienceFee()).div( - controller.convenienceFeeMax() - ), - earned.mul(2) - ); - } - - function _test_swap_and_check_balances( - address fromPickleJar, - address toPickleJar, - address fromPickleJarUnderlying, - uint256 fromPickleJarUnderlyingAmount, - address payable[] memory targets, - bytes[] memory data - ) internal { - uint256 _beforeTo = IERC20(toPickleJar).balanceOf(address(this)); - uint256 _beforeFrom = IERC20(fromPickleJar).balanceOf(address(this)); - - uint256 _beforeDev = IERC20(fromPickleJarUnderlying).balanceOf(devfund); - uint256 _beforeTreasury = IERC20(fromPickleJarUnderlying).balanceOf( - treasury - ); - - uint256 _ret = controller.swapExactJarForJar( - fromPickleJar, - toPickleJar, - fromPickleJarUnderlyingAmount, - 0, // Min receive amount - targets, - data - ); - - uint256 _afterTo = IERC20(toPickleJar).balanceOf(address(this)); - uint256 _afterFrom = IERC20(fromPickleJar).balanceOf(address(this)); - - uint256 _afterDev = IERC20(fromPickleJarUnderlying).balanceOf(devfund); - uint256 _afterTreasury = IERC20(fromPickleJarUnderlying).balanceOf( - treasury - ); - - uint256 treasuryEarned = _afterTreasury.sub(_beforeTreasury); - - assertEqApprox(treasuryEarned, _afterDev.sub(_beforeDev)); - assertTrue(treasuryEarned > 0); - _test_check_treasury_fee(fromPickleJarUnderlyingAmount, treasuryEarned); - assertTrue(_afterFrom < _beforeFrom); - assertTrue(_afterTo > _beforeTo); - assertTrue(_afterTo.sub(_beforeTo) > 0); - assertEqApprox(_afterTo.sub(_beforeTo), _ret); - assertEq(_afterFrom, 0); - } - - function _get_uniswap_pl_swap_data(address from, address to) - internal pure - returns (bytes memory) - { - return - abi.encodeWithSignature("swapUniswap(address,address)", from, to); - } - - function _test_curve_curve( - uint256 fromIndex, - uint256 toIndex, - uint256 amount, - address payable[] memory targets, - bytes[] memory data - ) public { - // Get LP - _getCurveLP(curvePools[fromIndex], amount); - - // Deposit into pickle jars - address from = address(curvePickleJars[fromIndex].token()); - uint256 _from = IERC20(from).balanceOf(address(this)); - IERC20(from).approve(address(curvePickleJars[fromIndex]), _from); - curvePickleJars[fromIndex].deposit(_from); - curvePickleJars[fromIndex].earn(); - - hevm.warp(block.timestamp + 1 days); - hevm.roll(block.number + 6171); // Roughly number of blocks per day - - curveStrategies[fromIndex].harvest(); - - // Approve controller - uint256 _fromPickleJar = IERC20(address(curvePickleJars[fromIndex])) - .balanceOf(address(this)); - IERC20(address(curvePickleJars[fromIndex])).approve( - address(controller), - _fromPickleJar - ); - - // Swap - try - controller.swapExactJarForJar( - address(curvePickleJars[fromIndex]), - address(curvePickleJars[toIndex]), - _fromPickleJar, - uint256(-1), // Min receive amount - targets, - data - ) - { - revert("min-receive-amount"); - } catch {} - - _test_swap_and_check_balances( - address(curvePickleJars[fromIndex]), - address(curvePickleJars[toIndex]), - from, - _fromPickleJar, - targets, - data - ); - - _post_swap_check(fromIndex, toIndex); - } - - // **** Tests **** - - function test_jar_converter_curve_curve_0() public { - uint256 fromIndex = 0; - uint256 toIndex = 1; - uint256 amount = 400e18; - - int128 fromCurveUnderlyingIndex = 0; - - bytes4 toCurveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[4],uint256)" - ); - uint256 toCurvePoolSize = 4; - uint256 toCurveUnderlyingIndex = 0; - address toCurveUnderlying = dai; - - // Remove liquidity - address fromCurve = curvePools[fromIndex]; - address fromCurveLp = curveLps[fromIndex]; - - address payable target0 = payable(address(curveProxyLogic)); - bytes memory data0 = abi.encodeWithSignature( - "remove_liquidity_one_coin(address,address,int128)", - fromCurve, - fromCurveLp, - fromCurveUnderlyingIndex - ); - - // Add liquidity - address toCurve = curvePools[toIndex]; - - address payable target1 = payable(address(curveProxyLogic)); - bytes memory data1 = abi.encodeWithSignature( - "add_liquidity(address,bytes4,uint256,uint256,address)", - toCurve, - toCurveFunctionSig, - toCurvePoolSize, - toCurveUnderlyingIndex, - toCurveUnderlying - ); - - // Swap - _test_curve_curve( - fromIndex, - toIndex, - amount, - _getDynamicArray(target0, target1), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_curve_1() public { - uint256 fromIndex = 0; - uint256 toIndex = 2; - uint256 amount = 400e18; - - int128 fromCurveUnderlyingIndex = 0; - - bytes4 toCurveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[2],uint256)" - ); - uint256 toCurvePoolSize = 2; - uint256 toCurveUnderlyingIndex = 1; - address toCurveUnderlying = wbtc; - - // Remove liquidity - address fromCurve = curvePools[fromIndex]; - address fromCurveLp = curveLps[fromIndex]; - - bytes memory data0 = abi.encodeWithSignature( - "remove_liquidity_one_coin(address,address,int128)", - fromCurve, - fromCurveLp, - fromCurveUnderlyingIndex - ); - - // Swap - bytes memory data1 = _get_uniswap_pl_swap_data(dai, toCurveUnderlying); - - // Add liquidity - address toCurve = curvePools[toIndex]; - - bytes memory data2 = abi.encodeWithSignature( - "add_liquidity(address,bytes4,uint256,uint256,address)", - toCurve, - toCurveFunctionSig, - toCurvePoolSize, - toCurveUnderlyingIndex, - toCurveUnderlying - ); - - _test_curve_curve( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1, data2) - ); - } - - function test_jar_converter_curve_curve_2() public { - uint256 fromIndex = 1; - uint256 toIndex = 0; - uint256 amount = 400e18; - - int128 fromCurveUnderlyingIndex = 1; - - bytes4 toCurveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[3],uint256)" - ); - uint256 toCurvePoolSize = 3; - uint256 toCurveUnderlyingIndex = 2; - address toCurveUnderlying = usdt; - - // Remove liquidity - address fromCurve = susdv2_deposit; // curvePools[fromIndex]; - address fromCurveLp = curveLps[fromIndex]; - - bytes memory data0 = abi.encodeWithSignature( - "remove_liquidity_one_coin(address,address,int128)", - fromCurve, - fromCurveLp, - fromCurveUnderlyingIndex - ); - - // Swap - bytes memory data1 = _get_uniswap_pl_swap_data(usdc, usdt); - - // Add liquidity - address toCurve = curvePools[toIndex]; - - bytes memory data2 = abi.encodeWithSignature( - "add_liquidity(address,bytes4,uint256,uint256,address)", - toCurve, - toCurveFunctionSig, - toCurvePoolSize, - toCurveUnderlyingIndex, - toCurveUnderlying - ); - - _test_curve_curve( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1, data2) - ); - } - - function test_jar_converter_curve_curve_3() public { - uint256 fromIndex = 2; - uint256 toIndex = 0; - uint256 amount = 4e6; - - int128 fromCurveUnderlyingIndex = 1; - - bytes4 toCurveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[3],uint256)" - ); - uint256 toCurvePoolSize = 3; - uint256 toCurveUnderlyingIndex = 1; - address toCurveUnderlying = usdc; - - // Remove liquidity - address fromCurve = curvePools[fromIndex]; - address fromCurveLp = curveLps[fromIndex]; - - bytes memory data0 = abi.encodeWithSignature( - "remove_liquidity_one_coin(address,address,int128)", - fromCurve, - fromCurveLp, - fromCurveUnderlyingIndex - ); - - // Swap - bytes memory data1 = _get_uniswap_pl_swap_data(wbtc, usdc); - - // Add liquidity - address toCurve = curvePools[toIndex]; - - bytes memory data2 = abi.encodeWithSignature( - "add_liquidity(address,bytes4,uint256,uint256,address)", - toCurve, - toCurveFunctionSig, - toCurvePoolSize, - toCurveUnderlyingIndex, - toCurveUnderlying - ); - - _test_curve_curve( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1, data2) - ); - } - - function test_jar_converter_curve_curve_4() public { - uint256 fromIndex = 1; - uint256 toIndex = 0; - uint256 amount = 400e18; - - int128 fromCurveUnderlyingIndex = 2; - - bytes4 toCurveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[3],uint256)" - ); - uint256 toCurvePoolSize = 3; - uint256 toCurveUnderlyingIndex = 1; - address toCurveUnderlying = usdc; - - // Remove liquidity - address fromCurve = susdv2_deposit; - address fromCurveLp = curveLps[fromIndex]; - - bytes memory data0 = abi.encodeWithSignature( - "remove_liquidity_one_coin(address,address,int128)", - fromCurve, - fromCurveLp, - fromCurveUnderlyingIndex - ); - - // Swap - bytes memory data1 = _get_uniswap_pl_swap_data(usdt, usdc); - - // Add liquidity - address toCurve = curvePools[toIndex]; - - bytes memory data2 = abi.encodeWithSignature( - "add_liquidity(address,bytes4,uint256,uint256,address)", - toCurve, - toCurveFunctionSig, - toCurvePoolSize, - toCurveUnderlyingIndex, - toCurveUnderlying - ); - - _test_curve_curve( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1, data2) - ); - } -} diff --git a/src/tests/jar-converters/curve-uni.sol b/src/tests/jar-converters/curve-uni.sol deleted file mode 100644 index ed19925fd..000000000 --- a/src/tests/jar-converters/curve-uni.sol +++ /dev/null @@ -1,740 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; -pragma experimental ABIEncoderV2; - -import "../lib/hevm.sol"; -import "../lib/user.sol"; -import "../lib/test-approx.sol"; -import "../lib/test-defi-base.sol"; - -import "../../interfaces/strategy.sol"; -import "../../interfaces/curve.sol"; -import "../../interfaces/uniswapv2.sol"; - -import "../../pickle-jar.sol"; -import "../../controller-v4.sol"; - -import "../../proxy-logic/curve.sol"; -import "../../proxy-logic/uniswapv2.sol"; - -import "../../strategies/uniswapv2/strategy-uni-eth-dai-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-usdt-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-usdc-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-wbtc-lp-v2.sol"; - -import "../../strategies/curve/strategy-curve-scrv-v3_2.sol"; -import "../../strategies/curve/strategy-curve-rencrv-v2.sol"; -import "../../strategies/curve/strategy-curve-3crv-v2.sol"; - -contract StrategyCurveUniJarSwapTest is DSTestDefiBase { - address governance; - address strategist; - address devfund; - address treasury; - address timelock; - - IStrategy[] curveStrategies; - IStrategy[] uniStrategies; - - PickleJar[] curvePickleJars; - PickleJar[] uniPickleJars; - - ControllerV4 controller; - - CurveProxyLogic curveProxyLogic; - UniswapV2ProxyLogic uniswapV2ProxyLogic; - - address[] curvePools; - address[] curveLps; - - address[] uniUnderlying; - - // Contract wide variable to avoid stack too deep errors - uint256 temp; - - function setUp() public { - governance = address(this); - strategist = address(this); - devfund = address(new User()); - treasury = address(new User()); - timelock = address(this); - - controller = new ControllerV4( - governance, - strategist, - timelock, - devfund, - treasury - ); - - // Curve Strategies - curveStrategies = new IStrategy[](3); - curvePickleJars = new PickleJar[](curveStrategies.length); - curveLps = new address[](curveStrategies.length); - curvePools = new address[](curveStrategies.length); - - curveLps[0] = three_crv; - curvePools[0] = three_pool; - curveStrategies[0] = IStrategy( - address( - new StrategyCurve3CRVv2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - curveLps[1] = scrv; - curvePools[1] = susdv2_pool; - curveStrategies[1] = IStrategy( - address( - new StrategyCurveSCRVv3_2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - curveLps[2] = ren_crv; - curvePools[2] = ren_pool; - curveStrategies[2] = IStrategy( - address( - new StrategyCurveRenCRVv2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - // Create PICKLE Jars - for (uint256 i = 0; i < curvePickleJars.length; i++) { - curvePickleJars[i] = new PickleJar( - curveStrategies[i].want(), - governance, - timelock, - address(controller) - ); - - controller.setJar( - curveStrategies[i].want(), - address(curvePickleJars[i]) - ); - controller.approveStrategy( - curveStrategies[i].want(), - address(curveStrategies[i]) - ); - controller.setStrategy( - curveStrategies[i].want(), - address(curveStrategies[i]) - ); - } - - // Uni strategies - uniStrategies = new IStrategy[](4); - uniUnderlying = new address[](uniStrategies.length); - uniPickleJars = new PickleJar[](uniStrategies.length); - - uniUnderlying[0] = dai; - uniStrategies[0] = IStrategy( - address( - new StrategyUniEthDaiLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[1] = usdc; - uniStrategies[1] = IStrategy( - address( - new StrategyUniEthUsdcLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[2] = usdt; - uniStrategies[2] = IStrategy( - address( - new StrategyUniEthUsdtLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[3] = wbtc; - uniStrategies[3] = IStrategy( - address( - new StrategyUniEthWBtcLpV2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - for (uint256 i = 0; i < uniStrategies.length; i++) { - uniPickleJars[i] = new PickleJar( - uniStrategies[i].want(), - governance, - timelock, - address(controller) - ); - - controller.setJar( - uniStrategies[i].want(), - address(uniPickleJars[i]) - ); - controller.approveStrategy( - uniStrategies[i].want(), - address(uniStrategies[i]) - ); - controller.setStrategy( - uniStrategies[i].want(), - address(uniStrategies[i]) - ); - } - - curveProxyLogic = new CurveProxyLogic(); - uniswapV2ProxyLogic = new UniswapV2ProxyLogic(); - - controller.approveJarConverter(address(curveProxyLogic)); - controller.approveJarConverter(address(uniswapV2ProxyLogic)); - - hevm.warp(startTime); - } - - function _getCurveLP(address curve, uint256 amount) internal { - if (curve == ren_pool) { - _getERC20(wbtc, amount); - uint256 _wbtc = IERC20(wbtc).balanceOf(address(this)); - IERC20(wbtc).approve(curve, _wbtc); - - uint256[2] memory liquidity; - liquidity[1] = _wbtc; - ICurveFi_2(curve).add_liquidity(liquidity, 0); - } else { - _getERC20(dai, amount); - uint256 _dai = IERC20(dai).balanceOf(address(this)); - IERC20(dai).approve(curve, _dai); - - if (curve == three_pool) { - uint256[3] memory liquidity; - liquidity[0] = _dai; - ICurveFi_3(curve).add_liquidity(liquidity, 0); - } else { - uint256[4] memory liquidity; - liquidity[0] = _dai; - ICurveFi_4(curve).add_liquidity(liquidity, 0); - } - } - } - - function _get_primitive_to_lp_data( - address from, - address to, - address dustRecipient - ) internal pure returns (bytes memory) { - return - abi.encodeWithSignature( - "primitiveToLpTokens(address,address,address)", - from, - to, - dustRecipient - ); - } - - function _get_curve_remove_liquidity_data( - address curve, - address curveLP, - int128 index - ) internal pure returns (bytes memory) { - return - abi.encodeWithSignature( - "remove_liquidity_one_coin(address,address,int128)", - curve, - curveLP, - index - ); - } - - // Some post swap checks - // Checks if there's any leftover funds in the converter contract - function _post_swap_check(uint256 fromIndex, uint256 toIndex) internal { - IERC20 token0 = curvePickleJars[fromIndex].token(); - IUniswapV2Pair token1 = IUniswapV2Pair( - address(uniPickleJars[toIndex].token()) - ); - - uint256 MAX_DUST = 1000; - - // No funds left behind - assertEq(curvePickleJars[fromIndex].balanceOf(address(controller)), 0); - assertEq(uniPickleJars[toIndex].balanceOf(address(controller)), 0); - assertTrue(token0.balanceOf(address(controller)) < MAX_DUST); - assertTrue(token1.balanceOf(address(controller)) < MAX_DUST); - - // Curve -> UNI LP should be optimal supply - // Note: We refund the access, which is why its checking this balance - assertTrue(IERC20(token1.token0()).balanceOf(address(this)) < MAX_DUST); - assertTrue(IERC20(token1.token1()).balanceOf(address(this)) < MAX_DUST); - - // Make sure only controller can call 'withdrawForSwap' - try curveStrategies[fromIndex].withdrawForSwap(0) { - revert("!withdraw-for-swap-only-controller"); - } catch {} - } - - function _test_check_treasury_fee(uint256 _amount, uint256 earned) - internal - { - assertEqApprox( - _amount.mul(controller.convenienceFee()).div( - controller.convenienceFeeMax() - ), - earned.mul(2) - ); - } - - function _test_swap_and_check_balances( - address fromPickleJar, - address toPickleJar, - address fromPickleJarUnderlying, - uint256 fromPickleJarUnderlyingAmount, - address payable[] memory targets, - bytes[] memory data - ) internal { - uint256 _beforeTo = IERC20(toPickleJar).balanceOf(address(this)); - uint256 _beforeFrom = IERC20(fromPickleJar).balanceOf(address(this)); - - uint256 _beforeDev = IERC20(fromPickleJarUnderlying).balanceOf(devfund); - uint256 _beforeTreasury = IERC20(fromPickleJarUnderlying).balanceOf( - treasury - ); - - uint256 _ret = controller.swapExactJarForJar( - fromPickleJar, - toPickleJar, - fromPickleJarUnderlyingAmount, - 0, // Min receive amount - targets, - data - ); - - uint256 _afterTo = IERC20(toPickleJar).balanceOf(address(this)); - uint256 _afterFrom = IERC20(fromPickleJar).balanceOf(address(this)); - - uint256 _afterDev = IERC20(fromPickleJarUnderlying).balanceOf(devfund); - uint256 _afterTreasury = IERC20(fromPickleJarUnderlying).balanceOf( - treasury - ); - - uint256 treasuryEarned = _afterTreasury.sub(_beforeTreasury); - - assertEqApprox(treasuryEarned, _afterDev.sub(_beforeDev)); - assertTrue(treasuryEarned > 0); - _test_check_treasury_fee(fromPickleJarUnderlyingAmount, treasuryEarned); - assertTrue(_afterFrom < _beforeFrom); - assertTrue(_afterTo > _beforeTo); - assertTrue(_afterTo.sub(_beforeTo) > 0); - assertEqApprox(_afterTo.sub(_beforeTo), _ret); - assertEq(_afterFrom, 0); - } - - function _test_curve_uni_swap( - uint256 fromIndex, - uint256 toIndex, - uint256 amount, - address payable[] memory targets, - bytes[] memory data - ) internal { - // Deposit into PickleJars - address from = address(curvePickleJars[fromIndex].token()); - - _getCurveLP(curvePools[fromIndex], amount); - - uint256 _from = IERC20(from).balanceOf(address(this)); - IERC20(from).approve(address(curvePickleJars[fromIndex]), _from); - curvePickleJars[fromIndex].deposit(_from); - curvePickleJars[fromIndex].earn(); - - hevm.warp(block.timestamp + 1 days); - hevm.roll(block.number + 6171); // Roughly number of blocks per day - - curveStrategies[fromIndex].harvest(); - - // Swap! - uint256 _fromPickleJar = IERC20(address(curvePickleJars[fromIndex])) - .balanceOf(address(this)); - IERC20(address(curvePickleJars[fromIndex])).approve( - address(controller), - _fromPickleJar - ); - - // Check minimum amount - try - controller.swapExactJarForJar( - address(curvePickleJars[fromIndex]), - address(uniPickleJars[toIndex]), - _fromPickleJar, - uint256(-1), // Min receive amount - targets, - data - ) - { - revert("min-amount-should-fail"); - } catch {} - - _test_swap_and_check_balances( - address(curvePickleJars[fromIndex]), - address(uniPickleJars[toIndex]), - from, - _fromPickleJar, - targets, - data - ); - - _post_swap_check(fromIndex, toIndex); - } - - // **** Tests **** // - - function test_jar_converter_curve_uni_0_0() public { - uint256 fromIndex = 0; - uint256 toIndex = 0; - uint256 amount = 400e18; - - address fromUnderlying = dai; - int128 fromUnderlyingIndex = 0; - - address curvePool = curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_uni_0_1() public { - uint256 fromIndex = 0; - uint256 toIndex = 1; - uint256 amount = 400e18; - - address fromUnderlying = usdc; - int128 fromUnderlyingIndex = 1; - - address curvePool = curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_uni_0_2() public { - uint256 fromIndex = 0; - uint256 toIndex = 2; - uint256 amount = 400e18; - - address fromUnderlying = usdt; - int128 fromUnderlyingIndex = 2; - - address curvePool = curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_uni_0_3() public { - uint256 fromIndex = 0; - uint256 toIndex = 3; - uint256 amount = 400e18; - - address fromUnderlying = usdt; - int128 fromUnderlyingIndex = 2; - - address curvePool = curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_uni_1_0() public { - uint256 fromIndex = 1; - uint256 toIndex = 0; - uint256 amount = 400e18; - - address fromUnderlying = usdt; - int128 fromUnderlyingIndex = 2; - - address curvePool = susdv2_deposit; // curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_uni_1_1() public { - uint256 fromIndex = 1; - uint256 toIndex = 1; - uint256 amount = 400e18; - - address fromUnderlying = dai; - int128 fromUnderlyingIndex = 0; - - address curvePool = susdv2_deposit; // curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_uni_1_2() public { - uint256 fromIndex = 1; - uint256 toIndex = 2; - uint256 amount = 400e18; - - address fromUnderlying = dai; - int128 fromUnderlyingIndex = 0; - - address curvePool = susdv2_deposit; // curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_uni_1_3() public { - uint256 fromIndex = 1; - uint256 toIndex = 3; - uint256 amount = 400e18; - - address fromUnderlying = dai; - int128 fromUnderlyingIndex = 0; - - address curvePool = susdv2_deposit; // curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_curve_uni_2_3() public { - uint256 fromIndex = 2; - uint256 toIndex = 3; - uint256 amount = 4e6; - - address fromUnderlying = wbtc; - int128 fromUnderlyingIndex = 1; - - address curvePool = curvePools[fromIndex]; - address toUnderlying = uniUnderlying[toIndex]; - address toWant = univ2Factory.getPair(weth, toUnderlying); - - bytes memory data0 = _get_curve_remove_liquidity_data( - curvePool, - curveLps[fromIndex], - fromUnderlyingIndex - ); - - bytes memory data1 = _get_primitive_to_lp_data( - fromUnderlying, - toWant, - treasury - ); - - _test_curve_uni_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(curveProxyLogic)), - payable(address(uniswapV2ProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } -} diff --git a/src/tests/jar-converters/uni-curve.sol b/src/tests/jar-converters/uni-curve.sol deleted file mode 100644 index 4b46be7d5..000000000 --- a/src/tests/jar-converters/uni-curve.sol +++ /dev/null @@ -1,949 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; -pragma experimental ABIEncoderV2; - -import "../lib/hevm.sol"; -import "../lib/user.sol"; -import "../lib/test-approx.sol"; -import "../lib/test-defi-base.sol"; - -import "../../interfaces/strategy.sol"; -import "../../interfaces/curve.sol"; -import "../../interfaces/uniswapv2.sol"; - -import "../../pickle-jar.sol"; -import "../../controller-v4.sol"; - -import "../../proxy-logic/curve.sol"; -import "../../proxy-logic/uniswapv2.sol"; - -import "../../strategies/uniswapv2/strategy-uni-eth-dai-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-usdt-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-usdc-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-wbtc-lp-v2.sol"; - -import "../../strategies/curve/strategy-curve-scrv-v3_2.sol"; -import "../../strategies/curve/strategy-curve-rencrv-v2.sol"; -import "../../strategies/curve/strategy-curve-3crv-v2.sol"; - -contract StrategyUniCurveJarSwapTest is DSTestDefiBase { - address governance; - address strategist; - address devfund; - address treasury; - address timelock; - - IStrategy[] curveStrategies; - IStrategy[] uniStrategies; - - PickleJar[] curvePickleJars; - PickleJar[] uniPickleJars; - - ControllerV4 controller; - - CurveProxyLogic curveProxyLogic; - UniswapV2ProxyLogic uniswapV2ProxyLogic; - - address[] curvePools; - address[] curveLps; - - address[] uniUnderlying; - - // Contract wide variable to avoid stack too deep errors - uint256 temp; - - function setUp() public { - governance = address(this); - strategist = address(this); - devfund = address(new User()); - treasury = address(new User()); - timelock = address(this); - - controller = new ControllerV4( - governance, - strategist, - timelock, - devfund, - treasury - ); - - // Curve Strategies - curveStrategies = new IStrategy[](3); - curvePickleJars = new PickleJar[](curveStrategies.length); - curveLps = new address[](curveStrategies.length); - curvePools = new address[](curveStrategies.length); - - curveLps[0] = three_crv; - curvePools[0] = three_pool; - curveStrategies[0] = IStrategy( - address( - new StrategyCurve3CRVv2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - curveLps[1] = scrv; - curvePools[1] = susdv2_pool; - curveStrategies[1] = IStrategy( - address( - new StrategyCurveSCRVv3_2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - curveLps[2] = ren_crv; - curvePools[2] = ren_pool; - curveStrategies[2] = IStrategy( - address( - new StrategyCurveRenCRVv2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - // Create PICKLE Jars - for (uint256 i = 0; i < curvePickleJars.length; i++) { - curvePickleJars[i] = new PickleJar( - curveStrategies[i].want(), - governance, - timelock, - address(controller) - ); - - controller.setJar( - curveStrategies[i].want(), - address(curvePickleJars[i]) - ); - controller.approveStrategy( - curveStrategies[i].want(), - address(curveStrategies[i]) - ); - controller.setStrategy( - curveStrategies[i].want(), - address(curveStrategies[i]) - ); - } - - // Uni strategies - uniStrategies = new IStrategy[](4); - uniUnderlying = new address[](uniStrategies.length); - uniPickleJars = new PickleJar[](uniStrategies.length); - - uniUnderlying[0] = dai; - uniStrategies[0] = IStrategy( - address( - new StrategyUniEthDaiLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[1] = usdc; - uniStrategies[1] = IStrategy( - address( - new StrategyUniEthUsdcLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[2] = usdt; - uniStrategies[2] = IStrategy( - address( - new StrategyUniEthUsdtLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[3] = wbtc; - uniStrategies[3] = IStrategy( - address( - new StrategyUniEthWBtcLpV2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - for (uint256 i = 0; i < uniStrategies.length; i++) { - uniPickleJars[i] = new PickleJar( - uniStrategies[i].want(), - governance, - timelock, - address(controller) - ); - - controller.setJar( - uniStrategies[i].want(), - address(uniPickleJars[i]) - ); - controller.approveStrategy( - uniStrategies[i].want(), - address(uniStrategies[i]) - ); - controller.setStrategy( - uniStrategies[i].want(), - address(uniStrategies[i]) - ); - } - - curveProxyLogic = new CurveProxyLogic(); - uniswapV2ProxyLogic = new UniswapV2ProxyLogic(); - - controller.approveJarConverter(address(curveProxyLogic)); - controller.approveJarConverter(address(uniswapV2ProxyLogic)); - - hevm.warp(startTime); - } - - function _getUniLP( - address lp, - uint256 ethAmount, - uint256 otherAmount - ) internal { - IUniswapV2Pair fromPair = IUniswapV2Pair(lp); - - address other = fromPair.token0() != weth - ? fromPair.token0() - : fromPair.token1(); - - _getERC20(other, otherAmount); - - uint256 _other = IERC20(other).balanceOf(address(this)); - - IERC20(other).safeApprove(address(univ2), 0); - IERC20(other).safeApprove(address(univ2), _other); - - univ2.addLiquidityETH{value: ethAmount}( - other, - _other, - 0, - 0, - address(this), - now + 60 - ); - } - - function _get_uniswap_remove_liquidity_data(address pair) - internal - pure - returns (bytes memory) - { - return abi.encodeWithSignature("removeLiquidity(address)", pair); - } - - function _get_uniswap_lp_tokens_to_primitive(address from, address to) - internal - pure - returns (bytes memory) - { - return - abi.encodeWithSignature( - "lpTokensToPrimitive(address,address)", - from, - to - ); - } - - function _get_curve_add_liquidity_data( - address curve, - bytes4 curveFunctionSig, - uint256 curvePoolSize, - uint256 curveUnderlyingIndex, - address underlying - ) internal pure returns (bytes memory) { - return - abi.encodeWithSignature( - "add_liquidity(address,bytes4,uint256,uint256,address)", - curve, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - underlying - ); - } - - // Some post swap checks - // Checks if there's any leftover funds in the converter contract - function _post_swap_check(uint256 fromIndex, uint256 toIndex) internal { - IERC20 token0 = uniPickleJars[fromIndex].token(); - IERC20 token1 = curvePickleJars[toIndex].token(); - - // No funds left behind - assertEq(uniPickleJars[fromIndex].balanceOf(address(controller)), 0); - assertEq(curvePickleJars[toIndex].balanceOf(address(controller)), 0); - assertEq(token0.balanceOf(address(controller)), 0); - assertEq(token1.balanceOf(address(controller)), 0); - assertEq(IERC20(wbtc).balanceOf(address(controller)), 0); - // assertEq(IERC20(usdt).balanceOf(address(controller)), 0); - // assertEq(IERC20(usdc).balanceOf(address(controller)), 0); - // assertEq(IERC20(susd).balanceOf(address(controller)), 0); - // assertEq(IERC20(dai).balanceOf(address(controller)), 0); - - // No balance left behind! - assertEq(token1.balanceOf(address(this)), 0); - - // Make sure only controller can call 'withdrawForSwap' - try uniStrategies[fromIndex].withdrawForSwap(0) { - revert("!withdraw-for-swap-only-controller"); - } catch {} - } - - function _test_check_treasury_fee(uint256 _amount, uint256 earned) - internal - { - assertEqApprox( - _amount.mul(controller.convenienceFee()).div( - controller.convenienceFeeMax() - ), - earned.mul(2) - ); - } - - function _test_swap_and_check_balances( - address fromPickleJar, - address toPickleJar, - address fromPickleJarUnderlying, - uint256 fromPickleJarUnderlyingAmount, - address payable[] memory targets, - bytes[] memory data - ) internal { - uint256 _beforeTo = IERC20(toPickleJar).balanceOf(address(this)); - uint256 _beforeFrom = IERC20(fromPickleJar).balanceOf(address(this)); - - uint256 _beforeDev = IERC20(fromPickleJarUnderlying).balanceOf(devfund); - uint256 _beforeTreasury = IERC20(fromPickleJarUnderlying).balanceOf( - treasury - ); - - uint256 _ret = controller.swapExactJarForJar( - fromPickleJar, - toPickleJar, - fromPickleJarUnderlyingAmount, - 0, // Min receive amount - targets, - data - ); - - uint256 _afterTo = IERC20(toPickleJar).balanceOf(address(this)); - uint256 _afterFrom = IERC20(fromPickleJar).balanceOf(address(this)); - - uint256 _afterDev = IERC20(fromPickleJarUnderlying).balanceOf(devfund); - uint256 _afterTreasury = IERC20(fromPickleJarUnderlying).balanceOf( - treasury - ); - - uint256 treasuryEarned = _afterTreasury.sub(_beforeTreasury); - - assertEq(treasuryEarned, _afterDev.sub(_beforeDev)); - assertTrue(treasuryEarned > 0); - _test_check_treasury_fee(fromPickleJarUnderlyingAmount, treasuryEarned); - assertTrue(_afterFrom < _beforeFrom); - assertTrue(_afterTo > _beforeTo); - assertTrue(_afterTo.sub(_beforeTo) > 0); - assertEq(_afterTo.sub(_beforeTo), _ret); - assertEq(_afterFrom, 0); - } - - function _test_uni_curve_swap( - uint256 fromIndex, - uint256 toIndex, - uint256 amount, - address payable[] memory targets, - bytes[] memory data - ) internal { - // Deposit into PickleJars - address from = address(uniPickleJars[fromIndex].token()); - - _getUniLP(from, 1e18, amount); - - uint256 _from = IERC20(from).balanceOf(address(this)); - IERC20(from).approve(address(uniPickleJars[fromIndex]), _from); - uniPickleJars[fromIndex].deposit(_from); - uniPickleJars[fromIndex].earn(); - - hevm.warp(block.timestamp + 1 days); - hevm.roll(block.number + 6171); // Roughly number of blocks per day - - uniStrategies[fromIndex].harvest(); - - // Swap! - uint256 _fromPickleJar = IERC20(address(uniPickleJars[fromIndex])) - .balanceOf(address(this)); - IERC20(address(uniPickleJars[fromIndex])).approve( - address(controller), - _fromPickleJar - ); - - // Check minimum amount - try - controller.swapExactJarForJar( - address(uniPickleJars[fromIndex]), - address(curvePickleJars[toIndex]), - _fromPickleJar, - uint256(-1), // Min receive amount - targets, - data - ) - { - revert("min-amount-should-fail"); - } catch {} - - _test_swap_and_check_balances( - address(uniPickleJars[fromIndex]), - address(curvePickleJars[toIndex]), - from, - _fromPickleJar, - targets, - data - ); - - _post_swap_check(fromIndex, toIndex); - } - - // **** Tests **** // - - function test_jar_converter_uni_curve_0_0() public { - uint256 fromIndex = 0; - uint256 toIndex = 0; - uint256 amount = 400e18; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 3; - address curveUnderlying = dai; - uint256 curveUnderlyingIndex = 0; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[3],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_1_0() public { - uint256 fromIndex = 1; - uint256 toIndex = 0; - uint256 amount = 400e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 3; - address curveUnderlying = dai; - uint256 curveUnderlyingIndex = 0; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[3],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_2_0() public { - uint256 fromIndex = 2; - uint256 toIndex = 0; - uint256 amount = 400e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 3; - address curveUnderlying = dai; - uint256 curveUnderlyingIndex = 0; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[3],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_3_0() public { - uint256 fromIndex = 3; - uint256 toIndex = 0; - uint256 amount = 4e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 3; - address curveUnderlying = dai; - uint256 curveUnderlyingIndex = 0; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[3],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_0_1() public { - uint256 fromIndex = 0; - uint256 toIndex = 1; - uint256 amount = 400e18; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 4; - address curveUnderlying = usdt; - uint256 curveUnderlyingIndex = 2; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[4],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_1_1() public { - uint256 fromIndex = 1; - uint256 toIndex = 1; - uint256 amount = 400e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 4; - address curveUnderlying = usdt; - uint256 curveUnderlyingIndex = 2; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[4],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_2_1() public { - uint256 fromIndex = 2; - uint256 toIndex = 1; - uint256 amount = 400e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 4; - address curveUnderlying = usdt; - uint256 curveUnderlyingIndex = 2; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[4],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_3_1() public { - uint256 fromIndex = 3; - uint256 toIndex = 1; - uint256 amount = 4e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 4; - address curveUnderlying = usdt; - uint256 curveUnderlyingIndex = 2; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[4],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_4_1() public { - uint256 fromIndex = 3; - uint256 toIndex = 1; - uint256 amount = 4e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 4; - address curveUnderlying = usdt; - uint256 curveUnderlyingIndex = 2; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[4],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_0_2() public { - uint256 fromIndex = 0; - uint256 toIndex = 2; - uint256 amount = 400e18; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 2; - address curveUnderlying = wbtc; - uint256 curveUnderlyingIndex = 1; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[2],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_1_2() public { - uint256 fromIndex = 1; - uint256 toIndex = 2; - uint256 amount = 400e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 2; - address curveUnderlying = wbtc; - uint256 curveUnderlyingIndex = 1; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[2],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_2_2() public { - uint256 fromIndex = 2; - uint256 toIndex = 2; - uint256 amount = 400e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 2; - address curveUnderlying = wbtc; - uint256 curveUnderlyingIndex = 1; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[2],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - - function test_jar_converter_uni_curve_3_2() public { - uint256 fromIndex = 3; - uint256 toIndex = 2; - uint256 amount = 4e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - - address curvePool = curvePools[toIndex]; - uint256 curvePoolSize = 2; - address curveUnderlying = wbtc; - uint256 curveUnderlyingIndex = 1; - bytes4 curveFunctionSig = _getFunctionSig( - "add_liquidity(uint256[2],uint256)" - ); - - bytes memory data0 = _get_uniswap_lp_tokens_to_primitive( - univ2Factory.getPair(weth, fromUnderlying), - curveUnderlying - ); - - bytes memory data1 = _get_curve_add_liquidity_data( - curvePool, - curveFunctionSig, - curvePoolSize, - curveUnderlyingIndex, - curveUnderlying - ); - - _test_uni_curve_swap( - fromIndex, - toIndex, - amount, - _getDynamicArray( - payable(address(uniswapV2ProxyLogic)), - payable(address(curveProxyLogic)) - ), - _getDynamicArray(data0, data1) - ); - } - -} diff --git a/src/tests/jar-converters/uni-uni.test.sol b/src/tests/jar-converters/uni-uni.test.sol deleted file mode 100644 index 0f578389e..000000000 --- a/src/tests/jar-converters/uni-uni.test.sol +++ /dev/null @@ -1,393 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.7; -pragma experimental ABIEncoderV2; - -import "../lib/hevm.sol"; -import "../lib/user.sol"; -import "../lib/test-approx.sol"; -import "../lib/test-defi-base.sol"; - -import "../../interfaces/strategy.sol"; -import "../../interfaces/curve.sol"; -import "../../interfaces/uniswapv2.sol"; - -import "../../pickle-jar.sol"; -import "../../controller-v4.sol"; - -import "../../proxy-logic/curve.sol"; -import "../../proxy-logic/uniswapv2.sol"; - -import "../../strategies/uniswapv2/strategy-uni-eth-dai-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-usdt-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-usdc-lp-v4.sol"; -import "../../strategies/uniswapv2/strategy-uni-eth-wbtc-lp-v2.sol"; - -contract StrategyUniUniJarSwapTest is DSTestDefiBase { - address governance; - address strategist; - address devfund; - address treasury; - address timelock; - - IStrategy[] uniStrategies; - PickleJar[] uniPickleJars; - - ControllerV4 controller; - - CurveProxyLogic curveProxyLogic; - UniswapV2ProxyLogic uniswapV2ProxyLogic; - - address[] uniUnderlying; - - function setUp() public { - governance = address(this); - strategist = address(this); - devfund = address(new User()); - treasury = address(new User()); - timelock = address(this); - - controller = new ControllerV4( - governance, - strategist, - timelock, - devfund, - treasury - ); - - // Uni strategies - uniStrategies = new IStrategy[](4); - uniUnderlying = new address[](uniStrategies.length); - uniPickleJars = new PickleJar[](uniStrategies.length); - - uniUnderlying[0] = dai; - uniStrategies[0] = IStrategy( - address( - new StrategyUniEthDaiLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[1] = usdc; - uniStrategies[1] = IStrategy( - address( - new StrategyUniEthUsdcLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[2] = usdt; - uniStrategies[2] = IStrategy( - address( - new StrategyUniEthUsdtLpV4( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - uniUnderlying[3] = wbtc; - uniStrategies[3] = IStrategy( - address( - new StrategyUniEthWBtcLpV2( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - for (uint256 i = 0; i < uniStrategies.length; i++) { - uniPickleJars[i] = new PickleJar( - uniStrategies[i].want(), - governance, - timelock, - address(controller) - ); - - controller.setJar( - uniStrategies[i].want(), - address(uniPickleJars[i]) - ); - controller.approveStrategy( - uniStrategies[i].want(), - address(uniStrategies[i]) - ); - controller.setStrategy( - uniStrategies[i].want(), - address(uniStrategies[i]) - ); - } - - curveProxyLogic = new CurveProxyLogic(); - uniswapV2ProxyLogic = new UniswapV2ProxyLogic(); - - controller.approveJarConverter(address(curveProxyLogic)); - controller.approveJarConverter(address(uniswapV2ProxyLogic)); - - hevm.warp(startTime); - } - - function _getUniLP( - address lp, - uint256 ethAmount, - uint256 otherAmount - ) internal { - IUniswapV2Pair fromPair = IUniswapV2Pair(lp); - - address other = fromPair.token0() != weth - ? fromPair.token0() - : fromPair.token1(); - - _getERC20(other, otherAmount); - - uint256 _other = IERC20(other).balanceOf(address(this)); - - IERC20(other).safeApprove(address(univ2), 0); - IERC20(other).safeApprove(address(univ2), _other); - - univ2.addLiquidityETH{value: ethAmount}( - other, - _other, - 0, - 0, - address(this), - now + 60 - ); - } - - function _get_swap_lp_data( - address from, - address to, - address dustRecipient - ) internal pure returns (bytes memory) { - return - abi.encodeWithSignature( - "swapUniLPTokens(address,address,address)", - from, - to, - dustRecipient - ); - } - - function _post_swap_check(uint256 fromIndex, uint256 toIndex) internal { - IERC20 token0 = uniPickleJars[fromIndex].token(); - IERC20 token1 = uniPickleJars[toIndex].token(); - - uint256 MAX_DUST = 10; - - // No funds left behind - assertEq(uniPickleJars[fromIndex].balanceOf(address(controller)), 0); - assertEq(uniPickleJars[toIndex].balanceOf(address(controller)), 0); - assertTrue(token0.balanceOf(address(controller)) < MAX_DUST); - assertTrue(token1.balanceOf(address(controller)) < MAX_DUST); - - // Make sure only controller can call 'withdrawForSwap' - try uniStrategies[fromIndex].withdrawForSwap(0) { - revert("!withdraw-for-swap-only-controller"); - } catch {} - } - - function _test_check_treasury_fee(uint256 _amount, uint256 earned) - internal - { - assertEqApprox( - _amount.mul(controller.convenienceFee()).div( - controller.convenienceFeeMax() - ), - earned.mul(2) - ); - } - - function _test_swap_and_check_balances( - address fromPickleJar, - address toPickleJar, - address fromPickleJarUnderlying, - uint256 fromPickleJarUnderlyingAmount, - address payable[] memory targets, - bytes[] memory data - ) internal { - uint256 _beforeTo = IERC20(toPickleJar).balanceOf(address(this)); - uint256 _beforeFrom = IERC20(fromPickleJar).balanceOf(address(this)); - - uint256 _beforeDev = IERC20(fromPickleJarUnderlying).balanceOf(devfund); - uint256 _beforeTreasury = IERC20(fromPickleJarUnderlying).balanceOf( - treasury - ); - - uint256 _ret = controller.swapExactJarForJar( - fromPickleJar, - toPickleJar, - fromPickleJarUnderlyingAmount, - 0, // Min receive amount - targets, - data - ); - - uint256 _afterTo = IERC20(toPickleJar).balanceOf(address(this)); - uint256 _afterFrom = IERC20(fromPickleJar).balanceOf(address(this)); - - uint256 _afterDev = IERC20(fromPickleJarUnderlying).balanceOf(devfund); - uint256 _afterTreasury = IERC20(fromPickleJarUnderlying).balanceOf( - treasury - ); - - uint256 treasuryEarned = _afterTreasury.sub(_beforeTreasury); - - assertEq(treasuryEarned, _afterDev.sub(_beforeDev)); - assertTrue(treasuryEarned > 0); - _test_check_treasury_fee(fromPickleJarUnderlyingAmount, treasuryEarned); - assertTrue(_afterFrom < _beforeFrom); - assertTrue(_afterTo > _beforeTo); - assertTrue(_afterTo.sub(_beforeTo) > 0); - assertEq(_afterTo.sub(_beforeTo), _ret); - assertEq(_afterFrom, 0); - } - - function _test_uni_uni( - uint256 fromIndex, - uint256 toIndex, - uint256 amount, - address payable[] memory targets, - bytes[] memory data - ) internal { - address from = address(uniPickleJars[fromIndex].token()); - - _getUniLP(from, 1e18, amount); - - uint256 _from = IERC20(from).balanceOf(address(this)); - IERC20(from).approve(address(uniPickleJars[fromIndex]), _from); - uniPickleJars[fromIndex].deposit(_from); - uniPickleJars[fromIndex].earn(); - - hevm.warp(block.timestamp + 1 days); - hevm.roll(block.number + 6171); // Roughly number of blocks per day - - uniStrategies[fromIndex].harvest(); - - // Swap! - uint256 _fromPickleJar = IERC20(address(uniPickleJars[fromIndex])) - .balanceOf(address(this)); - IERC20(address(uniPickleJars[fromIndex])).approve( - address(controller), - _fromPickleJar - ); - - // Check minimum amount - try - controller.swapExactJarForJar( - address(uniPickleJars[fromIndex]), - address(uniPickleJars[toIndex]), - _fromPickleJar, - uint256(-1), // Min receive amount - targets, - data - ) - { - revert("min-amount-should-fail"); - } catch {} - - _test_swap_and_check_balances( - address(uniPickleJars[fromIndex]), - address(uniPickleJars[toIndex]), - from, - _fromPickleJar, - targets, - data - ); - - _post_swap_check(fromIndex, toIndex); - } - - // **** Tests **** - - function test_jar_converter_uni_uni_0() public { - uint256 fromIndex = 0; - uint256 toIndex = 1; - uint256 amount = 400e18; - - address fromUnderlying = uniUnderlying[fromIndex]; - address from = univ2Factory.getPair(weth, fromUnderlying); - - address toUnderlying = uniUnderlying[toIndex]; - address to = univ2Factory.getPair(weth, toUnderlying); - - _test_uni_uni( - fromIndex, - toIndex, - amount, - _getDynamicArray(payable(address(uniswapV2ProxyLogic))), - _getDynamicArray(_get_swap_lp_data(from, to, treasury)) - ); - } - - function test_jar_converter_uni_uni_1() public { - uint256 fromIndex = 0; - uint256 toIndex = 2; - uint256 amount = 400e18; - - address fromUnderlying = uniUnderlying[fromIndex]; - address from = univ2Factory.getPair(weth, fromUnderlying); - - address toUnderlying = uniUnderlying[toIndex]; - address to = univ2Factory.getPair(weth, toUnderlying); - - _test_uni_uni( - fromIndex, - toIndex, - amount, - _getDynamicArray(payable(address(uniswapV2ProxyLogic))), - _getDynamicArray(_get_swap_lp_data(from, to, treasury)) - ); - } - - function test_jar_converter_uni_uni_2() public { - uint256 fromIndex = 2; - uint256 toIndex = 3; - uint256 amount = 400e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - address from = univ2Factory.getPair(weth, fromUnderlying); - - address toUnderlying = uniUnderlying[toIndex]; - address to = univ2Factory.getPair(weth, toUnderlying); - - _test_uni_uni( - fromIndex, - toIndex, - amount, - _getDynamicArray(payable(address(uniswapV2ProxyLogic))), - _getDynamicArray(_get_swap_lp_data(from, to, treasury)) - ); - } - - function test_jar_converter_uni_uni_3() public { - uint256 fromIndex = 3; - uint256 toIndex = 2; - uint256 amount = 4e6; - - address fromUnderlying = uniUnderlying[fromIndex]; - address from = univ2Factory.getPair(weth, fromUnderlying); - - address toUnderlying = uniUnderlying[toIndex]; - address to = univ2Factory.getPair(weth, toUnderlying); - - _test_uni_uni( - fromIndex, - toIndex, - amount, - _getDynamicArray(payable(address(uniswapV2ProxyLogic))), - _getDynamicArray(_get_swap_lp_data(from, to, treasury)) - ); - } -} diff --git a/src/tests/optimism/chainlinkKeeper.test.ts b/src/tests/optimism/chainlinkKeeper.test.ts new file mode 100644 index 000000000..3a69aa6a5 --- /dev/null +++ b/src/tests/optimism/chainlinkKeeper.test.ts @@ -0,0 +1,333 @@ +import "@nomicfoundation/hardhat-toolbox"; +import {ethers} from "hardhat"; +import {setBalance, loadFixture, mine} from "@nomicfoundation/hardhat-network-helpers"; +import {expect, getContractAt, deployContract, toWei, unlockAccount} from "../utils/testHelper"; +import {BigNumber, Contract} from "ethers"; +import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; + +describe(`PickleRebalancingKeeper`, () => { + const keeperContractName: string = "src/optimism/chainlinkKeeper.sol:PickleRebalancingKeeper"; + const uniV3StrategyContractName: string = + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-usdc-lp.sol:StrategyEthUsdcUniV3Optimism"; + const uniV3Strategy1Address: string = "0x1570B5D17a0796112263F4E3FAeee53459B41A49"; + const uniV3Strategy2Address: string = "0x754ece9AC6b3FF9aCc311261EC82Bd1B69b8E00B"; + const wethAddress: string = "0x4200000000000000000000000000000000000006"; + const strat1WethAmount: BigNumber = toWei(5000); + const strat2WethAmount: BigNumber = toWei(9000); + const univ3RouterAddress: string = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"; + const poolAbi = [ + "function observe(uint32[]) view returns(int56[], uint160[])", + "function tickSpacing() view returns(int24)", + "function token0() view returns(address)", + "function token1() view returns(address)", + "function slot0() view returns(uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)", + "function fee() view returns(uint24)", + ]; + const routerAbi = [ + "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) payable returns(uint256 amountOut)", + "function exactInput((bytes path,address recipient,uint256 amountIn,uint256 amountOutMinimum)) payable returns (uint256 amountOut)", + ]; + + const setupSingleStrategyFixture = async () => { + const [alice, governance] = await ethers.getSigners(); + + const weth = await getContractAt("src/lib/erc20.sol:ERC20", wethAddress); + + const strategy = await getContractAt(uniV3StrategyContractName, uniV3Strategy1Address); + const stratAddrEncoded = ethers.utils.defaultAbiCoder.encode(["address[]"], [[strategy.address]]); + + const poolAddress = await strategy.pool(); + const pool = await ethers.getContractAt(poolAbi, poolAddress); + + const token0 = await pool.token0(); + const token1 = await pool.token1(); + const strat1TokenOut: string = weth.address.toLowerCase() === token0.toLowerCase() ? token1 : token0; + + const router = await ethers.getContractAt(routerAbi, univ3RouterAddress); + + const keeper = await deployContract(keeperContractName, governance.address); + + // Add strategy to keeper watch-list + await keeper.connect(governance).addStrategies([strategy.address]); + + // Set alice weth balance so it can push strategy out of range + await setBalance(alice.address, strat1WethAmount.add(ethers.utils.parseEther("1"))); + const wethDepositAbi = ["function deposit() payable"]; + const wethTmp = await ethers.getContractAt(wethDepositAbi, wethAddress); + await wethTmp.connect(alice).deposit({value: strat1WethAmount}); + + // Add keeper to the strategy harvesters list + const stratStrategistAddr = await strategy.strategist(); + const stratStrategist = await unlockAccount(stratStrategistAddr); + await strategy.connect(stratStrategist).whitelistHarvesters([keeper.address]); + + return {alice, governance, weth, strategy, stratAddrEncoded, pool, strat1TokenOut, router, keeper}; + }; + + const setupDoubleStrategiesFixture = async () => { + const {alice, governance, weth, strategy, stratAddrEncoded, pool, strat1TokenOut, router, keeper} = + await loadFixture(setupSingleStrategyFixture); + + const strategy2 = await getContractAt(uniV3StrategyContractName, uniV3Strategy2Address); + + const poolAddress = await strategy2.pool(); + const strat2Pool = await ethers.getContractAt(poolAbi, poolAddress); + + const token0 = await strat2Pool.token0(); + const token1 = await strat2Pool.token1(); + const strat2TokenOut: string = weth.address.toLowerCase() === token0.toLowerCase() ? token1 : token0; + + // Add strategy to keeper watch-list + await keeper.connect(governance).addStrategies([strategy2.address]); + + // Set alice weth balance so it can push both strategies out of range + await setBalance(alice.address, strat2WethAmount.add(ethers.utils.parseEther("1"))); + const wethDepositAbi = ["function deposit() payable"]; + const wethTmp = await ethers.getContractAt(wethDepositAbi, wethAddress); + await wethTmp.connect(alice).deposit({value: strat2WethAmount}); + + // Add keeper to strategy2 harvesters list + const stratStrategistAddr = await strategy2.strategist(); + const stratStrategist = await unlockAccount(stratStrategistAddr); + await strategy2.connect(stratStrategist).whitelistHarvesters([keeper.address]); + + return { + alice, + governance, + weth, + strategy1: strategy, + strategy2, + stratAddrEncoded, + pool, + strat2Pool, + strat1TokenOut, + strat2TokenOut, + router, + keeper, + }; + }; + + describe("Keeper Strategies' Watch-List Behaviours", () => { + let pass: boolean; + + it("Only governance can remove strategies", async () => { + pass = false; + const {keeper, alice, strategy} = await loadFixture(setupSingleStrategyFixture); + await keeper + .connect(alice) + .removeStrategy(strategy.address) + .catch(() => { + pass = true; + }); + expect(pass).to.be.eq(true, "Non-governance address can remove strategy"); + }); + + it("Only governance can add strategies", async () => { + pass = true; + const {keeper, alice} = await loadFixture(setupSingleStrategyFixture); + await keeper + .connect(alice) + .addStrategies([uniV3Strategy2Address]) + .catch(() => { + pass = true; + }); + expect(pass).to.be.eq(true, "Non-governance address can add strategy"); + }); + + it("Should not add an already watched strategy", async () => { + pass = false; + const {keeper, governance, strategy} = await loadFixture(setupSingleStrategyFixture); + await keeper + .connect(governance) + .addStrategies([strategy.address]) + .catch(() => { + pass = true; + }); + expect(pass).to.be.eq(true, "Duplicate strategies can be added to keeper's watch list"); + }); + + it("Should not remove a non-watched strategy", async () => { + pass = false; + const {keeper, governance} = await loadFixture(setupSingleStrategyFixture); + await keeper + .connect(governance) + .removeStrategy(uniV3Strategy2Address) + .catch(() => { + pass = true; + }); + expect(pass).to.be.eq(true, "Non-watched strategy can be removed!"); + }); + + it("Should add a new strategy correctly", async () => { + const {keeper, governance} = await loadFixture(setupSingleStrategyFixture); + await keeper.connect(governance).addStrategies([uniV3Strategy2Address]).catch(); + const newStratAddress = await keeper.strategies(1); + expect(newStratAddress).to.be.eq(uniV3Strategy2Address, "Failed adding new strategy"); + }); + }); + + describe("checkUpkeep", () => { + it("Should return false when no rebalance needed", async () => { + const {keeper} = await loadFixture(setupSingleStrategyFixture); + const [shouldUpkeep] = await keeper.callStatic.checkUpkeep("0x"); + expect(shouldUpkeep).to.be.eq(false, "checkUpkeep logic broken"); + }); + + it("Should return true when a rebalance needed", async () => { + const {keeper, strategy, weth, strat1TokenOut, alice, router} = await loadFixture(setupSingleStrategyFixture); + + // Push strategy out of range + await pushOutOfRange(strategy, weth, strat1TokenOut, alice, router, strat1WethAmount); + + const [shouldUpkeep, data] = await keeper.callStatic.checkUpkeep("0x"); + expect(shouldUpkeep).to.be.eq(true, "checkUpkeep logic broken"); + + // In solidity, bytes variable is of type Uint8Array where each 32 bytes encodes a single value + // We need to convert it into a hex string so ethers abi coder can decode it + const hexString = ethers.utils.hexlify(data); + + const stratsToUpkeep = ethers.utils.defaultAbiCoder.decode( + ["address[]"], + ethers.utils.hexDataSlice(hexString, 0) + ) as [string[]]; + + expect(stratsToUpkeep[0].length).to.be.eq(1, "checkUpkeep thinks more than one strategy requires upkeeping"); + expect(stratsToUpkeep[0][0]).to.be.eq(strategy.address, "wrong strategy address returned"); + }); + }); + + describe("performUpkeep", () => { + let pass: boolean; + + it("Should prevent rebalance when within range", async () => { + const {keeper, governance, strategy, stratAddrEncoded} = await loadFixture(setupSingleStrategyFixture); + const upperTickBefore = await strategy.tick_upper(); + await keeper + .connect(governance) + .performUpkeep(stratAddrEncoded) + .catch(() => {}); + const upperTickAfter = await strategy.tick_upper(); + expect(upperTickBefore).to.be.eq(upperTickAfter, "Rebalance happened while within range"); + }); + + it("Should prevent rebalance when strategy not added to watchlist", async () => { + const {keeper, governance, strategy, stratAddrEncoded, weth, strat1TokenOut, alice, router} = await loadFixture( + setupSingleStrategyFixture + ); + // Push strategy out of range + await pushOutOfRange(strategy, weth, strat1TokenOut, alice, router, strat1WethAmount); + + pass = false; + await keeper.connect(governance).removeStrategy(strategy.address); + await keeper + .connect(governance) + .performUpkeep(stratAddrEncoded) + .catch(() => { + pass = true; + }); + expect(pass).to.be.eq(true, "Rebalanced a non-watched strategy"); + expect(await shouldRebalance(strategy.address)).to.be.eq(true, "Rebalanced a non-watched strategy"); + }); + + it("Should prevent rebalance when keeper is disabled", async () => { + pass = false; + const {keeper, governance, strategy, stratAddrEncoded, weth, strat1TokenOut, alice, router} = await loadFixture( + setupSingleStrategyFixture + ); + await pushOutOfRange(strategy, weth, strat1TokenOut, alice, router, strat1WethAmount); + await keeper.connect(governance).setDisabled(true); + expect(await keeper.disabled()).to.be.eq(true, "governance failed to disable the keeper"); + await keeper + .connect(governance) + .performUpkeep(stratAddrEncoded) + .catch(() => { + pass = true; + }); + expect(pass).to.be.eq(true, "Rebalanced while keeper is disabled"); + expect(await shouldRebalance(strategy.address)).to.be.eq(true, "Rebalanced while keeper is disabled"); + }); + + it("Should perform a rebalance successfully", async () => { + const {keeper, governance, strategy, weth, strat1TokenOut, alice, router} = await loadFixture( + setupSingleStrategyFixture + ); + await pushOutOfRange(strategy, weth, strat1TokenOut, alice, router, strat1WethAmount); + const [, data] = await keeper.checkUpkeep("0x"); + await keeper + .connect(governance) + .performUpkeep(data) + .catch(() => {}); + expect(await shouldRebalance(strategy.address)).to.be.eq(false, "Rebalance unsuccessful"); + }); + + it("Should rebalance multiple strategies successfully", async () => { + const {alice, governance, weth, strategy1, strategy2, strat1TokenOut, strat2TokenOut, router, keeper} = + await loadFixture(setupDoubleStrategiesFixture); + await pushOutOfRange(strategy1, weth, strat1TokenOut, alice, router, strat1WethAmount); + await pushOutOfRange(strategy2, weth, strat2TokenOut, alice, router, strat2WethAmount); + const [, data] = await keeper.checkUpkeep("0x"); + + await keeper + .connect(governance) + .performUpkeep(data) + .catch(() => {}); + expect(await shouldRebalance(strategy1.address)).to.be.eq(false, "Strategy1 rebalance unsuccessful"); + expect(await shouldRebalance(strategy2.address)).to.be.eq(false, "Strategy2 rebalance unsuccessful"); + }); + }); + + const shouldRebalance = async (stratAddr: string): Promise => { + const strategyContract = await getContractAt(uniV3StrategyContractName, stratAddr); + const poolContract = await ethers.getContractAt(poolAbi, await strategyContract.pool()); + + const upperTick = await strategyContract.tick_upper(); + const lowerTick = await strategyContract.tick_lower(); + const range = upperTick - lowerTick; + const limitVar = range / 10; + const lowerLimit = lowerTick + limitVar; + const upperLimit = upperTick - limitVar; + const [, currentTick] = await poolContract.slot0(); + + let shouldRebalance = false; + if (currentTick < lowerLimit || currentTick > upperLimit) shouldRebalance = true; + + return shouldRebalance; + }; + + const pushOutOfRange = async ( + strategy: Contract, + weth: Contract, + tokenOut: string, + alice: SignerWithAddress, + router: Contract, + wethAmount: BigNumber + ) => { + const poolContract = await ethers.getContractAt(poolAbi, await strategy.pool()); + + const fee = await poolContract.fee(); + const pathEncoded = ethers.utils.solidityPack(["address", "uint24", "address"], [weth.address, fee, tokenOut]); + const exactInputParams = [pathEncoded, alice.address, wethAmount, 0]; + + //console.log("Performing swap (be patient!)"); + const allowance = await weth.allowance(alice.address, router.address); + if (allowance.lt(wethAmount)) { + await weth.connect(alice).approve(router.address, 0); + await weth.connect(alice).approve(router.address, ethers.constants.MaxUint256); + } + await router.connect(alice).exactInput(exactInputParams); + //console.log("Swap successful"); + + // Forward blocks a bit so strategy.determineTicks() can adjust properly + await mine(1000); + + expect(await shouldRebalance(strategy.address)).to.be.eq( + true, + "Couldn't push strategy out of balance. Consider a larger trade size (Hint: increase wethAmount)" + ); + }; +}); + +process.on("unhandledRejection", (err) => { + console.log(err); + process.exit(1); +}); diff --git a/src/tests/optimism/minichefProxy.test.ts b/src/tests/optimism/minichefProxy.test.ts new file mode 100644 index 000000000..5ca0b0b8b --- /dev/null +++ b/src/tests/optimism/minichefProxy.test.ts @@ -0,0 +1,143 @@ +import "@nomicfoundation/hardhat-toolbox"; +import {ethers} from "hardhat"; +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect, deployContract, toWei} from "../utils/testHelper"; +import {BigNumber} from "ethers"; + +describe("MiniChefController", () => { + const setupFixture = async () => { + const [alice, governance, strategist] = await ethers.getSigners(); + + const pickle = await deployContract("src/yield-farming/pickle-token.sol:PickleToken"); + const reward = await deployContract("src/yield-farming/pickle-token.sol:PickleToken"); + await pickle.mint(governance.address, toWei(10_000)); + await reward.mint(governance.address, toWei(10_000)); + + const minichef = await deployContract("src/optimism/minichefv2.sol:MiniChefV2", pickle.address); + const rewarder = await deployContract( + "src/optimism/PickleRewarder.sol:PickleRewarder", + reward.address, + 0, + minichef.address + ); + + const jar1 = await deployContract("src/yield-farming/pickle-token.sol:PickleToken"); + const jar2 = await deployContract("src/yield-farming/pickle-token.sol:PickleToken"); + await jar1.mint(alice.address, toWei(10_000)); + await jar2.mint(alice.address, toWei(10_000)); + + // Deploy the controller and transfer governance + const miniChefController = await deployContract( + "src/optimism/minichefProxy.sol:ChefProxy", + minichef.address, + rewarder.address + ); + await miniChefController.setPendingGovernance(governance.address); + await miniChefController.connect(governance).claimGovernance(); + + // Transfer minichef & rewarder ownership to the controller + await minichef.transferOwnership(miniChefController.address, true, false); + await rewarder.transferOwnership(miniChefController.address, true, false); + + // Add strategist on the controller + await miniChefController.connect(governance).addStrategist(strategist.address); + + return {alice, governance, strategist, pickle, reward, minichef, rewarder, miniChefController, jar1, jar2}; + }; + + describe("Controller behaviour", () => { + it("Should add new strategists", async () => { + const {alice, governance, miniChefController} = await loadFixture(setupFixture); + + await miniChefController.connect(governance).addStrategist(alice.address); + expect(await miniChefController.isStrategist(alice.address)).to.be.eq(true, "Strategist was not added"); + }); + + it("Should remove strategists", async () => { + const {strategist, governance, miniChefController} = await loadFixture(setupFixture); + + await miniChefController.connect(governance).removeStrategist(strategist.address); + expect(await miniChefController.isStrategist(strategist.address)).to.be.eq(false, "Strategist was not removed"); + }); + + it("Should transfer governance correctly", async () => { + const {alice, governance, miniChefController} = await loadFixture(setupFixture); + + await miniChefController.connect(governance).setPendingGovernance(alice.address); + await miniChefController.connect(alice).claimGovernance(); + expect(await miniChefController.governance()).to.be.eq(alice.address, "Governance was not transferred"); + }); + + it("Should change chef/rewarder addresses correctly", async () => { + const {alice, governance, miniChefController} = await loadFixture(setupFixture); + + await miniChefController.connect(governance).setMinichef(alice.address); + expect(await miniChefController.MINICHEF()).to.be.eq(alice.address, "Minichef address was not changed"); + + await miniChefController.connect(governance).setRewarder(alice.address); + expect(await miniChefController.REWARDER()).to.be.eq(alice.address, "Rewarder address was not changed"); + }); + + it("Only governance can change chef owner", async () => { + const {alice, governance, miniChefController, minichef, rewarder} = await loadFixture(setupFixture); + + await miniChefController + .connect(alice) + .transferMinichefOwnership(alice.address) + .catch(() => {}); + expect(await minichef.owner()).to.be.eq(miniChefController.address, "Non-governance changed minichef owner"); + + await miniChefController + .connect(alice) + .transferRewarderOwnership(alice.address) + .catch(() => {}); + expect(await rewarder.owner()).to.be.eq(miniChefController.address, "Non-governance changed rewarder owner"); + + await miniChefController.connect(governance).transferMinichefOwnership(alice.address); + await minichef.connect(alice).claimOwnership(); + expect(await minichef.owner()).to.be.eq(alice.address, "Governance failed to change minichef owner"); + + await miniChefController.connect(governance).transferRewarderOwnership(alice.address); + await rewarder.connect(alice).claimOwnership(); + expect(await rewarder.owner()).to.be.eq(alice.address, "Governance failed to change rewarder owner"); + }); + + it("Should add multiple pools properly", async () => { + const {strategist, miniChefController, minichef, rewarder, jar1, jar2} = await loadFixture(setupFixture); + + await miniChefController.connect(strategist).add([jar1.address, jar2.address], [10, 20]); + expect(await minichef.lpToken(1)).to.be.eq(jar2.address, "LPs were not added properly on the minichef"); + expect((await minichef.poolInfo(1)).allocPoint).to.be.eq(20, "AllocPoints were not set properly on the minichef"); + expect(await rewarder.poolIds(1)).to.be.eq(1, "LPs were not added properly on the rewarder"); + expect((await rewarder.poolInfo(1)).allocPoint).to.be.eq(20, "AllocPoints were not set properly on the rewarder"); + }); + + it("Should set emission rates correctly", async () => { + const {strategist, miniChefController, minichef, rewarder} = await loadFixture(setupFixture); + + await miniChefController.connect(strategist).setPicklePerSecond(10001); + expect(await minichef.picklePerSecond()).to.be.eq( + BigNumber.from(10001), + "Emission rate was not set properly on the minichef" + ); + await miniChefController.connect(strategist).setRewardPerSecond(10002); + expect(await rewarder.rewardPerSecond()).to.be.eq( + BigNumber.from(10002), + "Emission rate was not set properly on the rewarder" + ); + }); + + it("Should execute emergency properly", async () => { + const {governance, miniChefController, minichef} = await loadFixture(setupFixture); + + // Renounce minichef ownership + const signature = "transferOwnership(address,bool,bool)"; + const data = ethers.utils.defaultAbiCoder.encode( + ["address", "bool", "bool"], + [ethers.constants.AddressZero, true, true] + ); + await miniChefController.connect(governance).execute(minichef.address, signature, data); + expect(await minichef.owner()).to.be.eq(ethers.constants.AddressZero, "Emergency execute failed"); + }); + }); +}); diff --git a/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-axlusdc-usdc-sslp.test.ts b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-axlusdc-usdc-sslp.test.ts new file mode 100644 index 000000000..d51ba0770 --- /dev/null +++ b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-axlusdc-usdc-sslp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/arbitrum/sushiswap/strategy-sushi-axlusdc-usdc-sslp.sol:StrategyArbSushiAxlusdcUsdcSslp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-dpx-eth-slp.test.ts b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-dpx-eth-slp.test.ts new file mode 100644 index 000000000..5e5e6354d --- /dev/null +++ b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-dpx-eth-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/arbitrum/sushiswap/strategy-sushi-dpx-eth-slp.sol:StrategyArbSushiDpxEthSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-eth-arb-scplp.test.ts b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-eth-arb-scplp.test.ts new file mode 100644 index 000000000..90fc34836 --- /dev/null +++ b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-eth-arb-scplp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/arbitrum/sushiswap/strategy-sushi-eth-arb-scplp.sol:StrategyArbSushiEthArbScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-eth-gohm-slp.test.ts b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-eth-gohm-slp.test.ts new file mode 100644 index 000000000..b8b81f748 --- /dev/null +++ b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-eth-gohm-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/arbitrum/sushiswap/strategy-sushi-eth-gohm-slp.sol:StrategyArbSushiEthGohmSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-slp.test.ts b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-slp.test.ts new file mode 100644 index 000000000..24b2249a3 --- /dev/null +++ b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/arbitrum/sushiswap/strategy-sushi-magic-eth-slp.sol:StrategyArbSushiMagicEthSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-rdpx-eth-slp.test.ts b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-rdpx-eth-slp.test.ts new file mode 100644 index 000000000..1565e1192 --- /dev/null +++ b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-rdpx-eth-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/arbitrum/sushiswap/strategy-sushi-rdpx-eth-slp.sol:StrategyArbSushiRdpxEthSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-usdt-usdc-sslp.test.ts b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-usdt-usdc-sslp.test.ts new file mode 100644 index 000000000..671145c79 --- /dev/null +++ b/src/tests/strategies/arbitrum/sushiswap/strategy-sushi-usdt-usdc-sslp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/arbitrum/sushiswap/strategy-sushi-usdt-usdc-sslp.sol:StrategyArbSushiUsdtUsdcSslp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/arbitrum/univ3/migrations/eth-usdc.test.ts b/src/tests/strategies/arbitrum/univ3/migrations/eth-usdc.test.ts new file mode 100644 index 000000000..61499845f --- /dev/null +++ b/src/tests/strategies/arbitrum/univ3/migrations/eth-usdc.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyUsdcEthUniV3Arbi Migration Test", () => { + const newStrategyContractName = + "src/strategies/arbitrum/uniswapv3/strategy-univ3-eth-usdc-lp.sol:StrategyUsdcEthUniV3Arbi"; + const oldStrategy = "0x41A610baad8BfdB620Badff488A034B06B13790D"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/arbitrum/univ3/migrations/gmx-eth.test.ts b/src/tests/strategies/arbitrum/univ3/migrations/gmx-eth.test.ts new file mode 100644 index 000000000..a3996d236 --- /dev/null +++ b/src/tests/strategies/arbitrum/univ3/migrations/gmx-eth.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyGmxEthUniV3Arbi Migration Test", () => { + const newStrategyContractName = + "src/strategies/arbitrum/uniswapv3/strategy-univ3-gmx-eth-lp.sol:StrategyGmxEthUniV3Arbi"; + const oldStrategy = "0x9C485ae43280dD0375C8c2290F1f77aee17CF512"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-btc-eth-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-btc-eth-slp.test.ts new file mode 100644 index 000000000..db6ca3b29 --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-btc-eth-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-btc-eth-slp.sol:StrategyFantomSushiBtcEthSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-crv-eth-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-crv-eth-slp.test.ts new file mode 100644 index 000000000..22e3c7f93 --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-crv-eth-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-crv-eth-slp.sol:StrategyFantomSushiCrvEthSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-eth-dai-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-eth-dai-slp.test.ts new file mode 100644 index 000000000..a7dc18e45 --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-eth-dai-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-eth-dai-slp.sol:StrategyFantomSushiEthDaiSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-eth-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-eth-slp.test.ts new file mode 100644 index 000000000..55631ae61 --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-eth-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-ftm-eth-slp.sol:StrategyFantomSushiFtmEthSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-link-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-link-slp.test.ts new file mode 100644 index 000000000..a39da7f74 --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-link-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-ftm-link-slp.sol:StrategyFantomSushiFtmLinkSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-sushi-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-sushi-slp.test.ts new file mode 100644 index 000000000..5be75632e --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-ftm-sushi-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-ftm-sushi-slp.sol:StrategyFantomSushiFtmSushiSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-fusdt-ftm-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-fusdt-ftm-slp.test.ts new file mode 100644 index 000000000..516497d0d --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-fusdt-ftm-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-fusdt-ftm-slp.sol:StrategyFantomSushiFusdtFtmSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-usdc-ftm-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-usdc-ftm-slp.test.ts new file mode 100644 index 000000000..45d1bb7e9 --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-usdc-ftm-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-usdc-ftm-slp.sol:StrategyFantomSushiUsdcFtmSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-usdc-mim-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-usdc-mim-slp.test.ts new file mode 100644 index 000000000..38819a8e0 --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-usdc-mim-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-usdc-mim-slp.sol:StrategyFantomSushiUsdcMimSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/fantom/sushiswap/strategy-sushi-yfi-eth-slp.test.ts b/src/tests/strategies/fantom/sushiswap/strategy-sushi-yfi-eth-slp.test.ts new file mode 100644 index 000000000..40bb70529 --- /dev/null +++ b/src/tests/strategies/fantom/sushiswap/strategy-sushi-yfi-eth-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/fantom/sushiswap/strategy-sushi-yfi-eth-slp.sol:StrategyFantomSushiYfiEthSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/kava/sushiswap/strategy-sushi-btc-eth-scplp.test.ts b/src/tests/strategies/kava/sushiswap/strategy-sushi-btc-eth-scplp.test.ts new file mode 100644 index 000000000..a9cf2c10a --- /dev/null +++ b/src/tests/strategies/kava/sushiswap/strategy-sushi-btc-eth-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/kava/sushiswap/strategy-sushi-btc-eth-scplp.sol:StrategyKavaSushiBtcEthScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-axlusdc-scplp.test.ts b/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-axlusdc-scplp.test.ts new file mode 100644 index 000000000..d8b312962 --- /dev/null +++ b/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-axlusdc-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/kava/sushiswap/strategy-sushi-kava-axlusdc-scplp.sol:StrategyKavaSushiKavaAxlusdcScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-eth-scplp.test.ts b/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-eth-scplp.test.ts new file mode 100644 index 000000000..448eedd07 --- /dev/null +++ b/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-eth-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/kava/sushiswap/strategy-sushi-kava-eth-scplp.sol:StrategyKavaSushiKavaEthScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-scplp.test.ts b/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-scplp.test.ts new file mode 100644 index 000000000..aba96ca1c --- /dev/null +++ b/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/kava/sushiswap/strategy-sushi-kava-scplp.sol:StrategyKavaSushiKavaScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-usdc-scplp.test.ts b/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-usdc-scplp.test.ts new file mode 100644 index 000000000..f38c5d5d6 --- /dev/null +++ b/src/tests/strategies/kava/sushiswap/strategy-sushi-kava-usdc-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/kava/sushiswap/strategy-sushi-kava-usdc-scplp.sol:StrategyKavaSushiKavaUsdcScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/optimism/beethovenx/strategy-beetx-ib-reth.test.ts b/src/tests/strategies/optimism/beethovenx/strategy-beetx-ib-reth.test.ts new file mode 100644 index 000000000..12938fb4f --- /dev/null +++ b/src/tests/strategies/optimism/beethovenx/strategy-beetx-ib-reth.test.ts @@ -0,0 +1,19 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { getWantFromWhale } from "../../../utils/setupHelper"; +import { doTestBehaviorBase } from "./strategyBeetxBase"; +import { ethers } from "hardhat"; +import { toWei } from "../../../utils/testHelper"; + + +describe("StrategyBeetxIbRethLp", () => { + const want_addr = "0x785F08fB77ec934c01736E30546f87B4daccBe50"; + const whale_addr = "0x4fbe899d37fb7514adf2f41B0630E018Ec275a0C"; + const reward_token_addr = "0xFE8B128bA8C78aabC59d4c64cEE7fF28e9379921"; + + before("Get want token", async () => { + const [alice] = await ethers.getSigners(); + await getWantFromWhale(want_addr, toWei(11, 16), alice, whale_addr); + }); + + doTestBehaviorBase("StrategyBeetxIbRethLp", want_addr, reward_token_addr, 5); +}); diff --git a/src/tests/strategies/optimism/beethovenx/strategyBeetxBase.ts b/src/tests/strategies/optimism/beethovenx/strategyBeetxBase.ts index 1c7f2c906..e14a06184 100644 --- a/src/tests/strategies/optimism/beethovenx/strategyBeetxBase.ts +++ b/src/tests/strategies/optimism/beethovenx/strategyBeetxBase.ts @@ -1,15 +1,10 @@ import "@nomicfoundation/hardhat-toolbox"; -import { ethers, network } from "hardhat"; -import { - expect, - increaseTime, - getContractAt, - increaseBlock, -} from "../../../utils/testHelper"; -import { setup } from "../../../utils/setupHelper"; -import { NULL_ADDRESS } from "../../../utils/constants"; -import { BigNumber, Contract } from "ethers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import {ethers, network} from "hardhat"; +import {expect, increaseTime, getContractAt, increaseBlock} from "../../../utils/testHelper"; +import {setup} from "../../../utils/setupHelper"; +import {NULL_ADDRESS} from "../../../utils/constants"; +import {BigNumber, Contract} from "ethers"; +import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; export const doTestBehaviorBase = ( strategyName: string, @@ -20,9 +15,7 @@ export const doTestBehaviorBase = ( isPolygon = false, blocktime = 5 ) => { - let alice: SignerWithAddress, - want: Contract, - native: Contract; + let alice: SignerWithAddress, want: Contract, native: Contract; let strategy: Contract, pickleJar: Contract, controller: Contract; let governance: SignerWithAddress, strategist: SignerWithAddress, @@ -56,25 +49,16 @@ export const doTestBehaviorBase = ( }); it("Should set the timelock correctly", async () => { - expect(await strategy.timelock()).to.be.eq( - timelock.address, - "timelock is incorrect" - ); + expect(await strategy.timelock()).to.be.eq(timelock.address, "timelock is incorrect"); await strategy.setTimelock(NULL_ADDRESS); - expect(await strategy.timelock()).to.be.eq( - NULL_ADDRESS, - "timelock is incorrect" - ); + expect(await strategy.timelock()).to.be.eq(NULL_ADDRESS, "timelock is incorrect"); }); it("Should withdraw correctly", async () => { const _want: BigNumber = await want.balanceOf(alice.address); await want.approve(pickleJar.address, _want); await pickleJar.deposit(_want); - console.log( - "Alice pTokenBalance after deposit: %s\n", - (await pickleJar.balanceOf(alice.address)).toString() - ); + console.log("Alice pTokenBalance after deposit: %s\n", (await pickleJar.balanceOf(alice.address)).toString()); await pickleJar.earn(); await increaseTime(60 * 60 * 24 * days); //travel days into the future @@ -82,47 +66,29 @@ export const doTestBehaviorBase = ( await increaseBlock((60 * 60 * 24 * days) / blocktime); //roughly days } - console.log( - "\nRatio before harvest: ", - (await pickleJar.getRatio()).toString() - ); + console.log("\nRatio before harvest: ", (await pickleJar.getRatio()).toString()); await strategy.harvest(); - console.log( - "Ratio after harvest: %s", - (await pickleJar.getRatio()).toString() - ); + console.log("Ratio after harvest: %s", (await pickleJar.getRatio()).toString()); let _before: BigNumber = await want.balanceOf(pickleJar.address); - console.log( - "\nPicklejar balance before controller withdrawal: ", - _before.toString() - ); + console.log("\nPicklejar balance before controller withdrawal: ", _before.toString()); await controller.withdrawAll(want.address); let _after: BigNumber = await want.balanceOf(pickleJar.address); - console.log( - "Picklejar balance after controller withdrawal: ", - _after.toString() - ); + console.log("Picklejar balance after controller withdrawal: ", _after.toString()); expect(_after).to.be.gt(_before, "controller withdrawAll failed"); _before = await want.balanceOf(alice.address); - console.log( - "\nAlice balance before picklejar withdrawal: ", - _before.toString() - ); + console.log("\nAlice balance before picklejar withdrawal: ", _before.toString()); await pickleJar.withdrawAll(); _after = await want.balanceOf(alice.address); - console.log( - "Alice balance after picklejar withdrawal: ", - _after.toString() - ); + console.log("Alice balance after picklejar withdrawal: ", _after.toString()); expect(_after).to.be.gt(_before, "picklejar withdrawAll failed"); expect(_after).to.be.gt(_want, "no interest earned"); @@ -132,18 +98,14 @@ export const doTestBehaviorBase = ( const _want: BigNumber = await want.balanceOf(alice.address); await want.approve(pickleJar.address, _want); await pickleJar.deposit(_want); - console.log( - "Alice pTokenBalance after deposit: %s\n", - (await pickleJar.balanceOf(alice.address)).toString() - ); + console.log("Alice pTokenBalance after deposit: %s\n", (await pickleJar.balanceOf(alice.address)).toString()); await pickleJar.earn(); await increaseTime(60 * 60 * 24 * days); //travel days into the future if (bIncreaseBlock) { await increaseBlock((60 * 60 * 24 * days) / blocktime); //roughly days } - const pendingRewards: [string[], BigNumber[]] = - await strategy.getHarvestable(); + const pendingRewards: [string[], BigNumber[]] = await strategy.getHarvestable(); const _before: BigNumber = await pickleJar.balance(); let _treasuryBefore: BigNumber = await native.balanceOf(treasury.address); @@ -152,62 +114,35 @@ export const doTestBehaviorBase = ( console.log(`\t${addr}: ${pendingRewards[1][i].toString()}`); }); console.log("Picklejar balance before harvest: ", _before.toString()); - console.log( - "💸 Treasury reward token balance before harvest: ", - _treasuryBefore.toString() - ); - console.log( - "\nRatio before harvest: ", - (await pickleJar.getRatio()).toString() - ); + console.log("💸 Treasury reward token balance before harvest: ", _treasuryBefore.toString()); + console.log("\nRatio before harvest: ", (await pickleJar.getRatio()).toString()); await strategy.harvest(); const _after: BigNumber = await pickleJar.balance(); let _treasuryAfter: BigNumber = await native.balanceOf(treasury.address); - console.log( - "Ratio after harvest: ", - (await pickleJar.getRatio()).toString() - ); + console.log("Ratio after harvest: ", (await pickleJar.getRatio()).toString()); console.log("\nPicklejar balance after harvest: ", _after.toString()); - console.log( - "💸 Treasury reward token balance after harvest: ", - _treasuryAfter.toString() - ); + console.log("💸 Treasury reward token balance after harvest: ", _treasuryAfter.toString()); //performance fee is given const rewardsEarned = _treasuryAfter.sub(_treasuryBefore); - console.log( - "\nPerformance fee earned by treasury: ", - rewardsEarned.toString() - ); + console.log("\nPerformance fee earned by treasury: ", rewardsEarned.toString()); expect(rewardsEarned).to.be.gt(0, "no performance fee taken"); //withdraw const _devBefore: BigNumber = await want.balanceOf(devfund.address); _treasuryBefore = await want.balanceOf(treasury.address); - console.log( - "\n👨‍🌾 Dev balance before picklejar withdrawal: ", - _devBefore.toString() - ); - console.log( - "💸 Treasury balance before picklejar withdrawal: ", - _treasuryBefore.toString() - ); + console.log("\n👨‍🌾 Dev balance before picklejar withdrawal: ", _devBefore.toString()); + console.log("💸 Treasury balance before picklejar withdrawal: ", _treasuryBefore.toString()); await pickleJar.withdrawAll(); const _devAfter: BigNumber = await want.balanceOf(devfund.address); _treasuryAfter = await want.balanceOf(treasury.address); - console.log( - "\n👨‍🌾 Dev balance after picklejar withdrawal: ", - _devAfter.toString() - ); - console.log( - "💸 Treasury balance after picklejar withdrawal: ", - _treasuryAfter.toString() - ); + console.log("\n👨‍🌾 Dev balance after picklejar withdrawal: ", _devAfter.toString()); + console.log("💸 Treasury balance after picklejar withdrawal: ", _treasuryAfter.toString()); //0% goes to dev const _devFund = _devAfter.sub(_devBefore); @@ -219,9 +154,7 @@ export const doTestBehaviorBase = ( }); it("Should perform multiple deposits and withdrawals correctly", async () => { - const _wantHalved: BigNumber = (await want.balanceOf(alice.address)).div( - 2 - ); + const _wantHalved: BigNumber = (await want.balanceOf(alice.address)).div(2); await want.connect(alice).transfer(strategist.address, _wantHalved); await want.connect(alice).approve(pickleJar.address, _wantHalved); await want.connect(strategist).approve(pickleJar.address, _wantHalved); @@ -248,51 +181,26 @@ export const doTestBehaviorBase = ( await pickleJar.connect(strategist).withdrawAll(); let _aliceBalanceAfter: BigNumber = await want.balanceOf(alice.address); - let _strategistBalanceAfter: BigNumber = await want.balanceOf( - strategist.address - ); - console.log( - "\nAlice balance after half withdrawal: %s\n", - _aliceBalanceAfter.toString() - ); - console.log( - "\nStrategist balance after half withdrawal: %s\n", - _strategistBalanceAfter.toString() - ); + let _strategistBalanceAfter: BigNumber = await want.balanceOf(strategist.address); + console.log("\nAlice balance after half withdrawal: %s\n", _aliceBalanceAfter.toString()); + console.log("\nStrategist balance after half withdrawal: %s\n", _strategistBalanceAfter.toString()); - expect(_aliceBalanceAfter).to.be.approximately( - _wantHalved.div(2), - 1, - "Alice withdrawal amount incorrect" - ); + expect(_aliceBalanceAfter).to.be.approximately(_wantHalved.div(2), 1, "Alice withdrawal amount incorrect"); - expect(_strategistBalanceAfter).to.be.approximately( - _wantHalved, - 1, - "Strategist withdrawal amount incorrect" - ); + expect(_strategistBalanceAfter).to.be.approximately(_wantHalved, 1, "Strategist withdrawal amount incorrect"); // Alice withdraws remainder await pickleJar.connect(alice).withdrawAll(); _aliceBalanceAfter = await want.balanceOf(alice.address); - console.log( - "\nAlice balance after full withdrawal: %s\n", - _aliceBalanceAfter.toString() - ); - expect(_aliceBalanceAfter).to.be.approximately( - _wantHalved, - 1, - "Alice withdrawal amount incorrect" - ); + console.log("\nAlice balance after full withdrawal: %s\n", _aliceBalanceAfter.toString()); + expect(_aliceBalanceAfter).to.be.approximately(_wantHalved, 1, "Alice withdrawal amount incorrect"); }); it("should add and remove rewards correctly", async () => { - const beetsBalOp = - "0xd6e5824b54f64ce6f1161210bc17eebffc77e031000100000000000000000006"; - const ethOpUsdc = - "0x39965c9dab5448482cf7e002f583c812ceb53046000100000000000000000003"; - + const beetsBalOp = "0xd6e5824b54f64ce6f1161210bc17eebffc77e031000100000000000000000006"; + const ethOpUsdc = "0x39965c9dab5448482cf7e002f583c812ceb53046000100000000000000000003"; + // Addresses and pool IDs const op = "0x4200000000000000000000000000000000000042"; const usdc = "0x7F5c764cBc14f9669B88837ca1490cCa17c31607"; @@ -304,31 +212,20 @@ export const doTestBehaviorBase = ( [beetsBalOp, ethOpUsdc], [reward_addr, op, native.address], ]; - const invalidToNativeRoute = [ - [ethOpUsdc], - [native.address, notRewardToken], - ]; + const invalidToNativeRoute = [[ethOpUsdc], [native.address, notRewardToken]]; // Arbitrary new reward route const arbNewRoute = [[ethOpUsdc], [notRewardToken, native.address]]; // Add reward tokens - const rewardsBeforeAdd: string[] = - await strategy.getActiveRewardsTokens(); + const rewardsBeforeAdd: string[] = await strategy.getActiveRewardsTokens(); await strategy.connect(alice).addToNativeRoute(...validToNativeRoute); await strategy.connect(alice).addToNativeRoute(...arbNewRoute); const rewardsAfterAdd: string[] = await strategy.getActiveRewardsTokens(); - expect(rewardsAfterAdd.length).to.be.eq( - rewardsBeforeAdd.length + 1, - "Adding reward failed" - ); - expect( - rewardsAfterAdd.filter( - (z) => z.toLowerCase() === reward_addr.toLowerCase() - ).length - ).to.be.eq( + expect(rewardsAfterAdd.length).to.be.eq(rewardsBeforeAdd.length + 1, "Adding reward failed"); + expect(rewardsAfterAdd.filter((z) => z.toLowerCase() === reward_addr.toLowerCase()).length).to.be.eq( 1, "Updating reward path results in redundance in activeRewardsTokens" ); @@ -341,10 +238,7 @@ export const doTestBehaviorBase = ( } catch (e) { callReverted = true; } - expect(callReverted).to.be.eq( - true, - "Strategy accepts adding reward with wrong route" - ); + expect(callReverted).to.be.eq(true, "Strategy accepts adding reward with wrong route"); // Perform a harvest and ensure it is successful const _want: BigNumber = await want.balanceOf(alice.address); @@ -361,20 +255,13 @@ export const doTestBehaviorBase = ( await strategy.harvest(); const ratioAfter = await pickleJar.getRatio(); - expect(ratioAfter).to.be.gt( - ratioBefore, - "Harvest failed after setting new toNative route" - ); + expect(ratioAfter).to.be.gt(ratioBefore, "Harvest failed after setting new toNative route"); // Remove a reward token await strategy.connect(alice).deactivateReward(reward_addr); - const rewardsAfterRemove: string[] = - await strategy.getActiveRewardsTokens(); + const rewardsAfterRemove: string[] = await strategy.getActiveRewardsTokens(); - expect(rewardsAfterRemove.length).to.be.eq( - rewardsAfterAdd.length - 1, - "Deactivating reward failed" - ); + expect(rewardsAfterRemove.length).to.be.eq(rewardsAfterAdd.length - 1, "Deactivating reward failed"); }); beforeEach(async () => { diff --git a/src/tests/strategies/optimism/sushiswap/strategy-sushi-eth-op-scplp.test.ts b/src/tests/strategies/optimism/sushiswap/strategy-sushi-eth-op-scplp.test.ts new file mode 100644 index 000000000..d132521b6 --- /dev/null +++ b/src/tests/strategies/optimism/sushiswap/strategy-sushi-eth-op-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/optimism/sushiswap/strategy-sushi-eth-op-scplp.sol:StrategyOpSushiEthOpScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/sushiswap/strategy-sushi-eth-usdc-scplp.test.ts b/src/tests/strategies/optimism/sushiswap/strategy-sushi-eth-usdc-scplp.test.ts new file mode 100644 index 000000000..9f9fa20c0 --- /dev/null +++ b/src/tests/strategies/optimism/sushiswap/strategy-sushi-eth-usdc-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/optimism/sushiswap/strategy-sushi-eth-usdc-scplp.sol:StrategyOpSushiEthUsdcScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/sushiswap/strategy-sushi-usdc-susd-sslp.test.ts b/src/tests/strategies/optimism/sushiswap/strategy-sushi-usdc-susd-sslp.test.ts new file mode 100644 index 000000000..fc9522c01 --- /dev/null +++ b/src/tests/strategies/optimism/sushiswap/strategy-sushi-usdc-susd-sslp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/optimism/sushiswap/strategy-sushi-usdc-susd-sslp.sol:StrategyOpSushiUsdcSusdSslp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/sushiswap/strategy-sushi-usdc-usdt-sslp.test.ts b/src/tests/strategies/optimism/sushiswap/strategy-sushi-usdc-usdt-sslp.test.ts new file mode 100644 index 000000000..45442038b --- /dev/null +++ b/src/tests/strategies/optimism/sushiswap/strategy-sushi-usdc-usdt-sslp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/optimism/sushiswap/strategy-sushi-usdc-usdt-sslp.sol:StrategyOpSushiUsdcUsdtSslp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/univ3/migration.test.ts b/src/tests/strategies/optimism/univ3/migration.test.ts new file mode 100644 index 000000000..f25b23123 --- /dev/null +++ b/src/tests/strategies/optimism/univ3/migration.test.ts @@ -0,0 +1,448 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { ethers } from "hardhat"; +import { loadFixture, mine, setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { BigNumber, Contract } from "ethers"; +import { deployContract, getContractAt } from "../../../utils/testHelper"; +import { callExecuteToProxy, sendGnosisSafeTxn } from "../../../utils/multisigHelper"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +const doRebalanceTestWithMigration = async ( + strategyContractName: string, + badStrategyAddress: string, + ethAmountToSwap: number, +) => { + describe("UniV3 Strategy", () => { + const initialSetupFixture = async () => { + const [governance, treasury, alice, bob, charles, fred] = await ethers.getSigners(); + + // Strategy setup + const strategy = await getContractAt(strategyContractName, badStrategyAddress); + + const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + + // Transfer governance on bad strategy + const timelockAddress = await strategy.governance(); + await sendGnosisSafeTxn(timelockAddress, strategy, "setGovernance", [governance.address]); + await sendGnosisSafeTxn(timelockAddress, strategy, "setTimelock", [governance.address]); + + return { strategy, governance, treasury, token0, token1, alice, bob, charles, fred }; + }; + + const setupRebalanceTestFixture = async () => { + const { governance, treasury, token0, token1, strategy: badStrat } = await loadFixture(initialSetupFixture); + + // Controller setup + // const controllerContractName = "/src/optimism/controller-v7.sol:ControllerV7"; + const controllerContractName = "/src/optimism/controller-v7.sol:ControllerV7"; + const controller = await deployContract(controllerContractName, + governance.address, + governance.address, + governance.address, + governance.address, + treasury.address + ) + + // Strategy setup + const poolAddr = await badStrat.pool(); + const poolAbi = ["function tickSpacing() view returns(int24)"]; + const pool = await ethers.getContractAt(poolAbi, poolAddr); + const tickSpacing = await pool.tickSpacing(); + const utick = await badStrat.tick_upper(); + const ltick = await badStrat.tick_lower(); + const tickRangeMultiplier = ((utick - ltick) / 2) / tickSpacing; + + const strategyContractFactory = await ethers.getContractFactory(strategyContractName); + const strategy = await strategyContractFactory.deploy( + tickRangeMultiplier, + governance.address, + governance.address, + controller.address, + governance.address + ); + + await controller.connect(governance).approveStrategy(poolAddr, strategy.address); + await controller.connect(governance).setStrategy(poolAddr, strategy.address); + + // Transfer Liquidity position from the bad strategy to the new one + const tokenId = await badStrat.tokenId(); + const nftManAddr = await badStrat.nftManager(); + const nftManAbi = [ + "function transferFrom(address from, address to, uint256 tokenId)", + "function ownerOf(uint256) view returns(address)", + ]; + const nftManContract = await ethers.getContractAt(nftManAbi, nftManAddr); + + await callExecuteToProxy(governance, badStrat, nftManContract, "transferFrom", [ + badStrat.address, + strategy.address, + tokenId, + ]); + + // transfer badstrat remaining usdt to the new strategy + const badStratBal0: BigNumber = await token0.balanceOf(badStrat.address); + const badStratBal1: BigNumber = await token1.balanceOf(badStrat.address); + if (!badStratBal0.isZero()) { + await callExecuteToProxy(governance, badStrat, token0, "transfer", [strategy.address, badStratBal0]) + } + if (!badStratBal1.isZero()) { + await callExecuteToProxy(governance, badStrat, token1, "transfer", [strategy.address, badStratBal1]) + } + + return { strategy, governance, token0, token1 }; + }; + const setupMigrationFixture = async () => { + const { governance, treasury, token0, token1, strategy: badStrat, alice, bob, charles, fred } = await loadFixture(initialSetupFixture); + + // Controller setup + // const controllerContractName = "/src/optimism/controller-v7.sol:ControllerV7"; + const controllerContractName = "/src/controller-v6.sol:ControllerV6"; + const controllerAddr = await badStrat.controller(); + const controller = await getContractAt(controllerContractName, controllerAddr); + const multiSigAddr = await controller.timelock(); + await sendGnosisSafeTxn(multiSigAddr, controller, "setGovernance", [governance.address]); + await sendGnosisSafeTxn(multiSigAddr, controller, "setTimelock", [governance.address]); + await controller.connect(governance).setStrategist(governance.address); + await controller.connect(governance).setTreasury(treasury.address); + + // Jar setup + const poolAddr = await badStrat.pool(); + const jarContract = "src/polygon/pickle-jar-univ3.sol:PickleJarUniV3Poly"; + const jarAddr = await controller.jars(poolAddr); + const jar = await getContractAt(jarContract, jarAddr); + + // Strategy setup + const poolAbi = ["function tickSpacing() view returns(int24)"]; + const pool = await ethers.getContractAt(poolAbi, poolAddr); + const tickSpacing = await pool.tickSpacing(); + const utick = await badStrat.tick_upper(); + const ltick = await badStrat.tick_lower(); + const tickRangeMultiplier = ((utick - ltick) / 2) / tickSpacing; + + const strategyContractFactory = await ethers.getContractFactory(strategyContractName); + const strategy = await strategyContractFactory.deploy( + tickRangeMultiplier, + governance.address, + governance.address, + controller.address, + governance.address + ); + + const native = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.native()); + + // Initial NFT creation + // UniV3 router + const routerAbi = [ + "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) payable returns(uint256 amountOut)", + "function exactInput((bytes path,address recipient,uint256 amountIn,uint256 amountOutMinimum)) payable returns (uint256 amountOut)", + "function WETH9() view returns(address)", + "function factory() view returns(address)", + ]; + const routerAddr = strategy.univ3Router(); + const router = await ethers.getContractAt(routerAbi, routerAddr); + await setBalance(governance.address, ethers.utils.parseEther(ethAmountToSwap.toString())); + await buyToken(governance, ethers.utils.parseEther((ethAmountToSwap / 5).toFixed(0)), token0, router); + await buyToken(governance, ethers.utils.parseEther((ethAmountToSwap / 5).toFixed(0)), token1, router); + await token0.connect(governance).transfer(strategy.address, await token0.balanceOf(governance.address)); + await token1.connect(governance).transfer(strategy.address, await token1.balanceOf(governance.address)); + await strategy.connect(governance).rebalance(); + + // Migrate liquidity + await controller.connect(governance).approveStrategy(poolAddr, strategy.address); + await controller.connect(governance).setStrategy(poolAddr, strategy.address); + await jar.earn(); + await strategy.connect(governance).rebalance(); + + // Set users' ETH balances so they can deposit in jars + const wethTopupAmount = ethers.utils.parseEther((ethAmountToSwap * 2 + 1).toString()); + await setBalance(alice.address, wethTopupAmount); + await setBalance(bob.address, wethTopupAmount); + await setBalance(charles.address, wethTopupAmount); + await setBalance(fred.address, wethTopupAmount); + + // Fill users' token0/token1 balances + await buyToken(alice, ethers.utils.parseEther(ethAmountToSwap.toString()), token0, router); + await buyToken(bob, ethers.utils.parseEther(ethAmountToSwap.toString()), token0, router); + await buyToken(charles, ethers.utils.parseEther(ethAmountToSwap.toString()), token0, router); + await buyToken(fred, ethers.utils.parseEther(ethAmountToSwap.toString()), token0, router); + await buyToken(alice, ethers.utils.parseEther(ethAmountToSwap.toString()), token1, router); + await buyToken(bob, ethers.utils.parseEther(ethAmountToSwap.toString()), token1, router); + await buyToken(charles, ethers.utils.parseEther(ethAmountToSwap.toString()), token1, router); + await buyToken(fred, ethers.utils.parseEther(ethAmountToSwap.toString()), token1, router); + + return { strategy, governance, treasury, jar, controller, token0, token1, native, alice, bob, charles, fred, router, tickRangeMultiplier }; + } + + describe("Strategy Rebalance", () => { + it.only("test rebalance on bad strategy", async () => { + const blockNumberBefore = await (await ethers.getSigners().then((x) => x[0])).provider?.getBlockNumber(); + const { governance, strategy, token0, token1 } = await loadFixture(initialSetupFixture); + + const token0name: string = await token0.symbol(); + const token1name: string = await token1.symbol(); + const token0decimals: number = await token0.decimals(); + const token1decimals: number = await token1.decimals(); + + const token0balBefore: BigNumber = await token0.balanceOf(strategy.address); + const token1balBefore: BigNumber = await token1.balanceOf(strategy.address); + const liquidityBefore: BigNumber = await strategy.liquidityOfPool(); + + await strategy.connect(governance).rebalance(); + + const token0balAfter: BigNumber = await token0.balanceOf(strategy.address); + const token1balAfter: BigNumber = await token1.balanceOf(strategy.address); + const liquidityAfter: BigNumber = await strategy.liquidityOfPool(); + + const blockNumberAfter = await governance.provider.getBlockNumber(); + + console.log("\n=== Before Rebalance ==="); + console.log("Block Number Before: " + blockNumberBefore); + console.log(token0name, "balance:", bigToNumber(token0balBefore, token0decimals)); + console.log(token1name, "balance:", bigToNumber(token1balBefore, token1decimals)); + console.log("Liquidity:", bigToNumber(liquidityBefore, 0)); + + console.log("\n=== After Rebalance ==="); + console.log("Block Number After: " + blockNumberAfter); + console.log(token0name, "balance:", bigToNumber(token0balAfter, token0decimals)); + console.log(token1name, "balance:", bigToNumber(token1balAfter, token1decimals)); + console.log("Liquidity:", bigToNumber(liquidityAfter, 0)); + }); + it.only("test rebalance on fixed strategy", async () => { + const blockNumberBefore = await (await ethers.getSigners().then((x) => x[0])).provider?.getBlockNumber(); + const { governance, strategy, token0, token1 } = await loadFixture(setupRebalanceTestFixture); + + const token0name: string = await token0.symbol(); + const token1name: string = await token1.symbol(); + const token0decimals: number = await token0.decimals(); + const token1decimals: number = await token1.decimals(); + + const token0balBefore: BigNumber = await token0.balanceOf(strategy.address); + const token1balBefore: BigNumber = await token1.balanceOf(strategy.address); + const liquidityBefore: BigNumber = await strategy.liquidityOfPool(); + + await strategy.connect(governance).rebalance(); + // await strategy.connect(governance).rebalance(); + + const token0balAfter: BigNumber = await token0.balanceOf(strategy.address); + const token1balAfter: BigNumber = await token1.balanceOf(strategy.address); + const liquidityAfter: BigNumber = await strategy.liquidityOfPool(); + + const blockNumberAfter = await governance.provider.getBlockNumber(); + + console.log("\n=== Before Rebalance ==="); + console.log("Block Number Before: " + blockNumberBefore); + console.log(token0name, "balance:", bigToNumber(token0balBefore, token0decimals)); + console.log(token1name, "balance:", bigToNumber(token1balBefore, token1decimals)); + console.log("Liquidity:", bigToNumber(liquidityBefore, 0)); + + console.log("\n=== After Rebalance ==="); + console.log("Block Number After: " + blockNumberAfter); + console.log(token0name, "balance:", bigToNumber(token0balAfter, token0decimals)); + console.log(token1name, "balance:", bigToNumber(token1balAfter, token1decimals)); + console.log("Liquidity:", bigToNumber(liquidityAfter, 0)); + }); + it("Should perform deposits and withdrawals correctly", async () => { + const { governance, treasury, strategy, controller, jar, token0, token1, native, alice, bob, charles, tickRangeMultiplier } = await loadFixture(setupMigrationFixture); + + const token0Decimals = await token0.decimals(); + const token1Decimals = await token1.decimals(); + + // Test setTokenToNative + // const wethAddr = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619"; + // const poolFee = 500; + // const wmaticAddr = "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270" + // const pathEncoded = ethers.utils.solidityPack(["address", "uint24", "address"], [wethAddr, poolFee, wmaticAddr]); + // await strategy.connect(governance).setTokenToNativeRoute(wethAddr,pathEncoded); + + console.log("Jar token0 balance before test => ", bigToNumber(await token0.balanceOf(jar.address), token0Decimals)); + console.log("Jar token1 balance before test => ", bigToNumber(await token1.balanceOf(jar.address), token1Decimals)); + console.log("Strategy token0 balance before test => ", bigToNumber(await token0.balanceOf(strategy.address), token0Decimals)); + console.log("Strategy token1 balance before test => ", bigToNumber(await token1.balanceOf(strategy.address), token1Decimals)); + + // Alice deposit + console.log("===============Alice Deposit=============="); + console.log("Alice token0 balance before deposit => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance before deposit => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + await depositIntoJar(alice, jar, token0, token1); + console.log("Alice token0 balance after deposit => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance after deposit => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + await strategy.connect(governance).setTickRangeMultiplier(Math.floor(tickRangeMultiplier / 2)); + console.log("Rebalancing..."); + await strategy.connect(governance).rebalance(); + + // Bob & Charles deposits + console.log("===============Bob Deposit=============="); + console.log("Bob token0 balance before deposit => ", bigToNumber(await token0.balanceOf(bob.address), token0Decimals)); + console.log("Bob token1 balance before deposit => ", bigToNumber(await token1.balanceOf(bob.address), token1Decimals)); + await depositIntoJar(bob, jar, token0, token1); + console.log("Bob token0 balance after deposit => ", bigToNumber(await token0.balanceOf(bob.address), token0Decimals)); + console.log("Bob token1 balance after deposit => ", bigToNumber(await token1.balanceOf(bob.address), token1Decimals)); + + console.log("===============Charles Deposit=============="); + console.log("Charles token0 balance before deposit => ", bigToNumber(await token0.balanceOf(charles.address), token0Decimals)); + console.log("Charles token1 balance before deposit => ", bigToNumber(await token1.balanceOf(charles.address), token1Decimals)); + await depositIntoJar(charles, jar, token0, token1); + console.log("Charles token0 balance after deposit => ", bigToNumber(await token0.balanceOf(charles.address), token0Decimals)); + console.log("Charles token1 balance after deposit => ", bigToNumber(await token1.balanceOf(charles.address), token1Decimals)); + + const aliceShare = await jar.balanceOf(alice.address); + const bobShare = await jar.balanceOf(bob.address); + const charlesShare = await jar.balanceOf(charles.address); + console.log("Alice share amount => ", bigToNumber(aliceShare)); + console.log("Bob share amount => ", bigToNumber(bobShare)); + console.log("Charles share amount => ", bigToNumber(charlesShare)); + + console.log("===============Alice partial withdraw=============="); + console.log("Alice token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + await jar.connect(alice).withdraw(aliceShare.div(BigNumber.from(2))); + console.log("Alice token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + console.log("Alice shares remaining => ", bigToNumber(await jar.balanceOf(alice.address))); + + await mine(60 * 60 * 24); + + console.log("===============Bob withdraw=============="); + console.log("Bob token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(bob.address), token0Decimals)); + console.log("Bob token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(bob.address), token1Decimals)); + await jar.connect(bob).withdrawAll(); + console.log("Bob token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(bob.address), token0Decimals)); + console.log("Bob token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(bob.address), token1Decimals)); + + console.log("Rebalancing..."); + await strategy.connect(governance).rebalance(); + + console.log("=============== Controller withdraw ==============="); + console.log("Jar token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(jar.address), token0Decimals)); + console.log("Jar token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(jar.address), token1Decimals)); + const poolAddr = strategy.pool(); + await controller.withdrawAll(poolAddr); + console.log("Jar token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(jar.address), token0Decimals)); + console.log("Jar token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(jar.address), token1Decimals)); + + console.log("===============Alice Full withdraw=============="); + console.log("Alice token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + await jar.connect(alice).withdrawAll(); + console.log("Alice token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + + console.log("=============== charles withdraw =============="); + console.log("Charles token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(charles.address), token0Decimals)); + console.log("Charles token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(charles.address), token1Decimals)); + await jar.connect(charles).withdrawAll(); + console.log("Charles token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(charles.address), token0Decimals)); + console.log("Charles token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(charles.address), token1Decimals)); + + console.log("------------------ Finished -----------------------"); + + console.log("Treasury token0 balance => ", bigToNumber(await token0.balanceOf(treasury.address), token0Decimals)); + console.log("Treasury token1 balance => ", bigToNumber(await token1.balanceOf(treasury.address), token1Decimals)); + console.log("Treasury native balance => ", bigToNumber(await native.balanceOf(treasury.address))); + console.log("Strategy token0 balance => ", bigToNumber(await token0.balanceOf(strategy.address), token0Decimals)); + console.log("Strategy token1 balance => ", bigToNumber(await token1.balanceOf(strategy.address), token1Decimals)); + console.log("Strategy native balance => ", bigToNumber(await native.balanceOf(strategy.address))); + console.log("Jar token0 balance => ", bigToNumber(await token0.balanceOf(jar.address), token0Decimals)); + console.log("Jar token1 balance => ", bigToNumber(await token1.balanceOf(jar.address), token1Decimals)); + console.log("Jar native balance => ", bigToNumber(await native.balanceOf(jar.address))); + }); + }); + }); + + const bigToNumber = (amount: BigNumber, decimals = 18) => parseFloat(ethers.utils.formatUnits(amount, decimals)); + + const buyToken = async (buyer: SignerWithAddress, ethAmountToSwap: BigNumber, token: Contract, router: Contract) => { + // Convert ETH to WETH + const wethAddr = await router.WETH9(); + const wethDepositAbi = ["function deposit() payable", "function approve(address,uint256) returns(bool)"]; + const weth = await ethers.getContractAt(wethDepositAbi, wethAddr); + await weth.connect(buyer).deposit({ value: ethAmountToSwap }); + + if (wethAddr === token.address) return; + + // Find the best pool for the swap + const factoryAbi = ["function getPool(address token0, address token1, uint24 fee) view returns(address)"]; + const factoryAddr = await router.factory(); + const factory = await ethers.getContractAt(factoryAbi, factoryAddr); + const fees = [100, 500, 3000]; + let bestPoolBal: BigNumber = BigNumber.from("0"); + let bestPoolFee: number; + for (let i = 0; i < fees.length; i++) { + const fee = fees[i]; + const poolAddr = await factory.getPool(wethAddr, token.address, fee); + if (poolAddr === ethers.constants.AddressZero) continue; + const poolBalance: BigNumber = await token.balanceOf(poolAddr); + if (poolBalance.gt(bestPoolBal)) { + bestPoolBal = poolBalance; + bestPoolFee = fee; + } + } + if (!bestPoolFee) throw `No pool found for WETH-${await token.symbol()}`; + + // Swap + const pathEncoded = ethers.utils.solidityPack(["address", "uint24", "address"], [wethAddr, bestPoolFee, token.address]); + const exactInputParams = [pathEncoded, buyer.address, ethAmountToSwap, 0]; + await weth.connect(buyer).approve(router.address, ethers.constants.MaxUint256); + await router.connect(buyer).exactInput(exactInputParams); + } + + const depositIntoJar = async (user: SignerWithAddress, jar: Contract, token0: Contract, token1: Contract) => { + const token0Bal = await token0.balanceOf(user.address); + const token1Bal = await token1.balanceOf(user.address); + await token0.connect(user).approve(jar.address, token0Bal); + await token1.connect(user).approve(jar.address, token1Bal); + await jar.connect(user).deposit(token0Bal, token1Bal); + }; + +}; + + +// Optimism USDC-DAI (tokenId 223464) +// doRebalanceTestWithMigration( +// "src/strategies/optimism/uniswapv3/strategy-univ3-usdc-dai-lp.sol:StrategyUsdcDaiUniV3Optimism", +// "0x387C985176A314c9e5D927a99724de98576812aF", +// 0.01 +// ) + +// Optimism SUSD-USDC (tokenId 223426) +// doRebalanceTestWithMigration( +// "src/strategies/optimism/uniswapv3/strategy-univ3-susd-usdc-lp.sol:StrategySusdUsdcUniV3Optimism", +// "0xa99e8a5754a53bE312Fba259c7C4619cfB00E849", +// 0.01 +// ) + +// Optimism SUSD-DAI (tokenId 223547) +// doRebalanceTestWithMigration( +// "src/strategies/optimism/uniswapv3/strategy-univ3-susd-dai-lp.sol:StrategySusdDaiUniV3Optimism", +// "0x1Bb40496D3074A2345d5e3Ac28b990854A7BDe34", +// 0.01 +// ) + +// Optimism ETH-USDC (tokenId 222765) +// doRebalanceTestWithMigration( +// "src/strategies/optimism/uniswapv3/strategy-univ3-eth-usdc-lp.sol:StrategyEthUsdcUniV3Optimism", +// "0x1570B5D17a0796112263F4E3FAeee53459B41A49", +// 0.01 +// ) + +// Optimism ETH-OP (tokenId 202867) +doRebalanceTestWithMigration( + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-op-lp.sol:StrategyEthOpUniV3Optimism", + "0x1634e17813D54Ffc7506523D6e8bf08556207468", + 0.01, +) + +// Optimism ETH-DAI (tokenId 222769) +// doRebalanceTestWithMigration( +// "src/strategies/optimism/uniswapv3/strategy-univ3-eth-dai-lp.sol:StrategyEthDaiUniV3Optimism", +// "0xE9936818ecd2a6930407a11C090260b5390A954d", +// 0.01, +// ) + +// Optimism ETH-BTC (tokenId 150971) +// doRebalanceTestWithMigration( +// "src/strategies/optimism/uniswapv3/strategy-univ3-eth-btc-lp.sol:StrategyEthBtcUniV3Optimism", +// "0x754ece9AC6b3FF9aCc311261EC82Bd1B69b8E00B", +// 0.01, +// ) + diff --git a/src/tests/strategies/optimism/univ3/migrations/eth-btc.test.ts b/src/tests/strategies/optimism/univ3/migrations/eth-btc.test.ts new file mode 100644 index 000000000..3d6ad837b --- /dev/null +++ b/src/tests/strategies/optimism/univ3/migrations/eth-btc.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyEthBtcUniV3Optimism Migration Test", () => { + const newStrategyContractName = + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-btc-lp.sol:StrategyEthBtcUniV3Optimism"; + const oldStrategy = "0x754ece9AC6b3FF9aCc311261EC82Bd1B69b8E00B"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/optimism/univ3/migrations/eth-dai.test.ts b/src/tests/strategies/optimism/univ3/migrations/eth-dai.test.ts new file mode 100644 index 000000000..4a663d353 --- /dev/null +++ b/src/tests/strategies/optimism/univ3/migrations/eth-dai.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyEthDaiUniV3Optimism Migration Test", () => { + const newStrategyContractName = + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-dai-lp.sol:StrategyEthDaiUniV3Optimism"; + const oldStrategy = "0xE9936818ecd2a6930407a11C090260b5390A954d"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/optimism/univ3/migrations/eth-op.test.ts b/src/tests/strategies/optimism/univ3/migrations/eth-op.test.ts new file mode 100644 index 000000000..a35a4f381 --- /dev/null +++ b/src/tests/strategies/optimism/univ3/migrations/eth-op.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyEthOpUniV3Optimism Migration Test", () => { + const newStrategyContractName = + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-op-lp.sol:StrategyEthOpUniV3Optimism"; + const oldStrategy = "0x1634e17813D54Ffc7506523D6e8bf08556207468"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/optimism/univ3/migrations/eth-usdc.test.ts b/src/tests/strategies/optimism/univ3/migrations/eth-usdc.test.ts new file mode 100644 index 000000000..e440e6a94 --- /dev/null +++ b/src/tests/strategies/optimism/univ3/migrations/eth-usdc.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyEthUsdcUniV3Optimism Migration Test", () => { + const newStrategyContractName = + "src/strategies/optimism/uniswapv3/strategy-univ3-eth-usdc-lp.sol:StrategyEthUsdcUniV3Optimism"; + const oldStrategy = "0x1570B5D17a0796112263F4E3FAeee53459B41A49"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/optimism/univ3/migrations/susd-dai.test.ts b/src/tests/strategies/optimism/univ3/migrations/susd-dai.test.ts new file mode 100644 index 000000000..1a0df7710 --- /dev/null +++ b/src/tests/strategies/optimism/univ3/migrations/susd-dai.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategySusdDaiUniV3Optimism Migration Test", () => { + const newStrategyContractName = + "src/strategies/optimism/uniswapv3/strategy-univ3-susd-dai-lp.sol:StrategySusdDaiUniV3Optimism"; + const oldStrategy = "0x1Bb40496D3074A2345d5e3Ac28b990854A7BDe34"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/optimism/univ3/migrations/susd-usdc.test.ts b/src/tests/strategies/optimism/univ3/migrations/susd-usdc.test.ts new file mode 100644 index 000000000..7641f3fb4 --- /dev/null +++ b/src/tests/strategies/optimism/univ3/migrations/susd-usdc.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategySusdUsdcUniV3Optimism Migration Test", () => { + const newStrategyContractName = + "src/strategies/optimism/uniswapv3/strategy-univ3-susd-usdc-lp.sol:StrategySusdUsdcUniV3Optimism"; + const oldStrategy = "0xa99e8a5754a53bE312Fba259c7C4619cfB00E849"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/optimism/univ3/migrations/usdc-dai.test.ts b/src/tests/strategies/optimism/univ3/migrations/usdc-dai.test.ts new file mode 100644 index 000000000..fb50c1e8a --- /dev/null +++ b/src/tests/strategies/optimism/univ3/migrations/usdc-dai.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyUsdcDaiUniV3Optimism Migration Test", () => { + const newStrategyContractName = + "src/strategies/optimism/uniswapv3/strategy-univ3-usdc-dai-lp.sol:StrategyUsdcDaiUniV3Optimism"; + const oldStrategy = "0x387C985176A314c9e5D927a99724de98576812aF"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-aleth-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-aleth-slp.test.ts index 0ee77b4fe..728c4e274 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-aleth-slp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-aleth-slp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-eth-aleth-slp.sol:StrategyVeloEthAlethSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloEthAlethSlp", () => { - const want_addr = "0x6fD5BEe1Ddb4dbBB0b7368B080Ab99b8BA765902"; - const whale_addr = "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 1710000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloEthAlethSlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-alien-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-alien-vlp.test.ts index a6a4167e2..7d72ef20b 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-alien-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-alien-vlp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-eth-alien-vlp.sol:StrategyVeloEthAlienVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloEthAlienVlp", () => { - const want_addr = "0x3EEc44e94ee86ce79f34Bb26dc3CdbbEe18d6d17"; - const whale_addr = "0xe247340f06FCB7eb904F16a48C548221375b5b96"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 110000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloEthAlienVlp", want_addr,reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-op-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-op-vlp.test.ts index 5254563b4..7e1e08321 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-op-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-op-vlp.test.ts @@ -1,19 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { toWei } from "../../../utils/testHelper"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-eth-op-vlp.sol:StrategyVeloEthOpVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloEthOpVlp", () => { - const want_addr = "0xcdd41009E74bD1AE4F7B2EeCF892e4bC718b9302"; - const whale_addr = "0x8E3E62E2229F49ab0A4F5028d05840BB89E20378"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, toWei(8,18), alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloEthOpVlp", want_addr,reward_token_addr, 5); -}); \ No newline at end of file +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-reth-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-reth-vlp.test.ts new file mode 100644 index 000000000..511ca2980 --- /dev/null +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-reth-vlp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "./strategyVeloBase"; + +const contract = "src/strategies/optimism/velodrome/strategy-velo-eth-reth-vlp.sol:StrategyVeloEthRethVlp"; +const shortName = contract.substring(contract.lastIndexOf(":") + 2); + +describe(shortName, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-seth-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-seth-slp.test.ts index 243695a15..caac50cf3 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-seth-slp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-seth-slp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-eth-seth-slp.sol:StrategyVeloEthSethSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloEthSethSlp", () => { - const want_addr = "0xFd7FddFc0A729eCF45fB6B12fA3B71A575E1966F"; - const whale_addr = "0x39F2A8e0c00d2f5f397A0ba0126544B05B972939"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 6500000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloEthSethSlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-usdc-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-usdc-vlp.test.ts index 7a73321f7..6df12bf32 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-eth-usdc-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-eth-usdc-vlp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-eth-usdc-vlp.sol:StrategyVeloEthUsdcVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloEthUsdcVlp", () => { - const want_addr = "0x79c912FEF520be002c2B6e57EC4324e260f38E50"; - const whale_addr = "0xbEbD2364664096c8d194194a209D81b62E37B13E"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 12000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloEthUsdcVlp", want_addr,reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-op-l2dao-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-op-l2dao-vlp.test.ts index 0e529d50c..89762a688 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-op-l2dao-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-op-l2dao-vlp.test.ts @@ -1,19 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; -import { toWei } from "../../../utils/testHelper"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-op-l2dao-vlp.sol:StrategyVeloOpL2daoVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloOpL2daoVlp", () => { - const want_addr = "0xfc77e39De40E54F820E313039207DC850E4C9E60"; - const whale_addr = "0x34e5a165aaB6e7B5dEEB3172BD49B29f2192dC68"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, toWei(2800, 18), alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloOpL2daoVlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-op-usdc-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-op-usdc-vlp.test.ts index afb581f40..c6f68dae4 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-op-usdc-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-op-usdc-vlp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-op-usdc-vlp.sol:StrategyVeloOpUsdcVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloOpUsdcVlp", () => { - const want_addr = "0x47029bc8f5CBe3b464004E87eF9c9419a48018cd"; - const whale_addr = "0x0333E3a9f758C43667b5FbBdB0d70173EAb34201"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 200000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloOpUsdcVlp", want_addr,reward_token_addr, 5); -}); \ No newline at end of file +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-op-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-op-vlp.test.ts index f3019532b..d16e93989 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-op-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-op-vlp.test.ts @@ -1,19 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { toWei } from "../../../utils/testHelper"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-op-vlp.sol:StrategyVeloOpVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloOpVlp", () => { - const want_addr = "0xFFD74EF185989BFF8752c818A53a47FC45388F08"; - const whale_addr = "0x924E36660060CaD83Cc438D0E91B0fb00C35eDC6"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, toWei(5000, 18), alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloOpVlp", want_addr,reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-ageur-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-ageur-vlp.test.ts index f3ea30fc9..4478f5cdf 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-ageur-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-ageur-vlp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-ageur-vlp.sol:StrategyVeloUsdcAgeurVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloUsdcAgeurVlp", () => { - const want_addr = "0x7866C6072B09539fC0FDE82963846b80203d7beb"; - const whale_addr = "0x603ffa42b2Ce5E22C1a2a7dafbD014F60567ebBC"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 66000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloUsdcAgeurVlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-alusd-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-alusd-slp.test.ts index e91fabe1e..43ff25fad 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-alusd-slp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-alusd-slp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-alusd-slp.sol:StrategyVeloUsdcAlusdSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloUsdcAlusdSlp", () => { - const want_addr = "0xe75a3f4Bf99882aD9f8aeBAB2115873315425D00"; - const whale_addr = "0x1431ba2D4e5C5ed4bf6E8Da41880A7e3B43dA32D"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 31000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloUsdcAlusdSlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-dai-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-dai-slp.test.ts new file mode 100644 index 000000000..2e4d01411 --- /dev/null +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-dai-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "./strategyVeloBase"; + +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-dai-slp.sol:StrategyVeloUsdcDaiSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-dola-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-dola-slp.test.ts new file mode 100644 index 000000000..b64e84e64 --- /dev/null +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-dola-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "./strategyVeloBase"; + +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-dola-slp.sol:StrategyVeloUsdcDolaSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-frax-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-frax-slp.test.ts index d829e4288..9c788f6fa 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-frax-slp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-frax-slp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-frax-slp.sol:StrategyVeloUsdcFraxSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloUsdcFraxSlp", () => { - const want_addr = "0xAdF902b11e4ad36B227B84d856B229258b0b0465"; - const whale_addr = "0x9702219A295801852357994DbfAEA84fcE3590Ca"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 92900, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloUsdcFraxSlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-lyra-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-lyra-vlp.test.ts index 7112df905..806ea03b4 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-lyra-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-lyra-vlp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-lyra-vlp.sol:StrategyVeloUsdcLyraVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloUsdcLyraVlp", () => { - const want_addr = "0xDEE1856D7B75Abf4C1bDf986da4e1C6c7864d640"; - const whale_addr = "0xf6657BBCfEB5C2746E8C609e35018A5a8caa13fb"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 1055, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloUsdcLyraVlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-mai-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-mai-slp.test.ts index 88574fdb2..394060539 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-mai-slp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-mai-slp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-mai-slp.sol:StrategyVeloUsdcMaiSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloUsdcMaiSlp", () => { - const want_addr = "0xd62C9D8a3D4fd98b27CaaEfE3571782a3aF0a737"; - const whale_addr = "0x6DEaC69777aFb8C27f24179AC5EB1795899b42C7"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 4900000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloUsdcMaiSlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-snx-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-snx-vlp.test.ts index db99bcc6d..369f360cd 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-snx-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-snx-vlp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-snx-vlp.sol:StrategyVeloUsdcSnxVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloUsdcSnxVlp", () => { - const want_addr = "0x9056EB7Ca982a5Dd65A584189994e6a27318067D"; - const whale_addr = "0x4023ef3aaa0669FaAf3A712626F4D8cCc3eAF2e5"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 1850000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloUsdcSnxVlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-susd-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-susd-slp.test.ts index 233c8e6bb..d8550bc2e 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-susd-slp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-susd-slp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-susd-slp.sol:StrategyVeloUsdcSusdSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloUsdcSusdSlp", () => { - const want_addr = "0xd16232ad60188B68076a235c65d692090caba155"; - const whale_addr = "0x040479ea0dD9499befF5b8E6b36948c91165EF38"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 270000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloUsdcSusdSlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-tusd-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-tusd-slp.test.ts new file mode 100644 index 000000000..cb96e814d --- /dev/null +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-tusd-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "./strategyVeloBase"; + +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-tusd-slp.sol:StrategyVeloUsdcTusdSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-vlp.test.ts index 125531d7d..fae837cd0 100644 --- a/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-vlp.test.ts +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-usdc-vlp.test.ts @@ -1,18 +1,7 @@ import "@nomicfoundation/hardhat-toolbox"; -import { getWantFromWhale } from "../../../utils/setupHelper"; import { doTestBehaviorBase } from "./strategyVeloBase"; -import { ethers } from "hardhat"; +const contract = "src/strategies/optimism/velodrome/strategy-velo-usdc-vlp.sol:StrategyVeloUsdcVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); -describe("StrategyVeloUsdcVlp", () => { - const want_addr = "0xe8537b6FF1039CB9eD0B71713f697DDbaDBb717d"; - const whale_addr = "0x40B322cEDd7AB3C379d65A55C51FE13A0B5A9016"; - const reward_token_addr = "0x3c8B650257cFb5f272f799F5e2b4e65093a11a05"; - - before("Get want token", async () => { - const [alice] = await ethers.getSigners(); - await getWantFromWhale(want_addr, 8000000000000000, alice, whale_addr); - }); - - doTestBehaviorBase("StrategyVeloUsdcVlp", want_addr, reward_token_addr, 5); -}); +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-wsteth-eth-vlp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-wsteth-eth-vlp.test.ts new file mode 100644 index 000000000..c6f532be3 --- /dev/null +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-wsteth-eth-vlp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "./strategyVeloBase"; + +const contract = "src/strategies/optimism/velodrome/strategy-velo-wsteth-eth-vlp.sol:StrategyVeloWstethEthVlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategy-velo-wsteth-seth-slp.test.ts b/src/tests/strategies/optimism/velodrome/strategy-velo-wsteth-seth-slp.test.ts new file mode 100644 index 000000000..995a5aab7 --- /dev/null +++ b/src/tests/strategies/optimism/velodrome/strategy-velo-wsteth-seth-slp.test.ts @@ -0,0 +1,7 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { doTestBehaviorBase } from "./strategyVeloBase"; + +const contract = "src/strategies/optimism/velodrome/strategy-velo-wsteth-seth-slp.sol:StrategyVeloWstethSethSlp"; +const name = contract.substring(contract.lastIndexOf(":") + 2); + +describe(name, () => doTestBehaviorBase(contract, 6)); diff --git a/src/tests/strategies/optimism/velodrome/strategyVeloBase.ts b/src/tests/strategies/optimism/velodrome/strategyVeloBase.ts index 2ee29fdd2..cb84dab50 100644 --- a/src/tests/strategies/optimism/velodrome/strategyVeloBase.ts +++ b/src/tests/strategies/optimism/velodrome/strategyVeloBase.ts @@ -1,59 +1,53 @@ import "@nomicfoundation/hardhat-toolbox"; -import { ethers, network } from "hardhat"; -import { - expect, - increaseTime, - getContractAt, - increaseBlock, -} from "../../../utils/testHelper"; +import { ethers } from "hardhat"; +import { expect, increaseTime, getContractAt } from "../../../utils/testHelper"; import { setup } from "../../../utils/setupHelper"; import { NULL_ADDRESS } from "../../../utils/constants"; import { BigNumber, Contract } from "ethers"; +import { loadFixture, setBalance } from "@nomicfoundation/hardhat-network-helpers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; export const doTestBehaviorBase = ( strategyName: string, - want_addr: string, - reward_addr: string, days = 15, - bIncreaseBlock = false, - isPolygon = false, - bloctime = 5 ) => { - let alice: SignerWithAddress, want: Contract, native: Contract; - let strategy: Contract, pickleJar: Contract, controller: Contract; - let governance: SignerWithAddress, - strategist: SignerWithAddress, - devfund: SignerWithAddress, - treasury: SignerWithAddress, - timelock: SignerWithAddress; - let preTestSnapshotID: any; - - describe(`${strategyName} common behavior tests`, () => { - before("Setup contracts", async () => { - [alice, devfund, treasury] = await ethers.getSigners(); - governance = alice; - strategist = devfund; - timelock = alice; - - want = await getContractAt("src/lib/erc20.sol:ERC20", want_addr); - - [controller, strategy, pickleJar] = await setup( + const shortName = strategyName.substring(strategyName.lastIndexOf(":") + 2); + describe(`${shortName} common behavior tests`, () => { + const initialSetupFixture = async () => { + const [governance, treasury, devfund] = await ethers.getSigners(); + + const alice = governance; + const strategist = devfund; + const timelock = alice; + + const [controller, strategy, jar] = await setup( strategyName, - want, governance, strategist, timelock, devfund, treasury, - isPolygon ); - const nativeAddr = await strategy.native(); - native = await getContractAt("src/lib/erc20.sol:ERC20", nativeAddr); - }); + const want = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.want()); + const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + const native = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.native()); + + // Get some want tokens into Alice wallet + await setBalance(alice.address, ethers.utils.parseEther("1.1")); + const wNativeDepositAbi = ["function deposit() payable", "function approve(address,uint256) returns(bool)"]; + const wNativeDeposit = await ethers.getContractAt(wNativeDepositAbi, native.address); + await wNativeDeposit.connect(alice).deposit({ value: ethers.utils.parseEther("1") }); + await getWantFor(alice, strategy); + const wantBalance:BigNumber = await want.balanceOf(alice.address); + expect(wantBalance.isZero()).to.be.eq(false, "Alice failed to get some want tokens!"); + + return { strategy, jar, controller, governance, treasury, timelock, devfund, strategist, alice, want, native, token0, token1 }; + }; it("Should set the timelock correctly", async () => { + const { strategy, timelock } = await loadFixture(initialSetupFixture); expect(await strategy.timelock()).to.be.eq( timelock.address, "timelock is incorrect" @@ -64,11 +58,56 @@ export const doTestBehaviorBase = ( "timelock is incorrect" ); }); + const getWantFor = async (signer: SignerWithAddress, strategy: Contract) => { + const routerAbi = [ + "function addLiquidity(address tokenA, address tokenB, bool stable, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline)", + // "function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, tuple(address from, address to, bool stable)[] routes, address to, uint256 deadline) returns (uint256[] amounts)", + {"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bool","name":"stable","type":"bool"}],"internalType":"struct Router.route[]","name":"routes","type":"tuple[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}, + ]; + const routerAddr = "0x9c12939390052919aF3155f41Bf4160Fd3666A6f"; + const router = await ethers.getContractAt(routerAbi, routerAddr); + const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + const native = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.native()); + const isStable = await strategy.isStablePool(); + + const nativeBal: BigNumber = await native.balanceOf(signer.address); + if (nativeBal.isZero()) throw "Signer have 0 native balance"; + + interface Route { from: string; to: string; stable: boolean; }; + const token0Routes: Route[] = []; + const token1Routes: Route[] = []; + for (let i = 0; i < 5; i++) { + if (token0.address === native.address) break; + const route0: Route = await strategy.nativeToTokenRoutes(token0.address, i); + token0Routes.push(route0); + if (route0.to === token0.address) break; + } + for (let i = 0; i < 5; i++) { + if (token1.address === native.address) break; + const route1: Route = await strategy.nativeToTokenRoutes(token1.address, i); + token1Routes.push(route1); + if (route1.to === token1.address) break; + } + + const deadline = Math.ceil(Date.now() / 1000) + 300; + + await native.connect(signer).approve(router.address, ethers.constants.MaxUint256); + token0.address !== native.address && await router.connect(signer).swapExactTokensForTokens(nativeBal.div(2), 0, token0Routes, signer.address, deadline); + token1.address !== native.address && await router.connect(signer).swapExactTokensForTokens(nativeBal.div(2), 0, token1Routes, signer.address, deadline); + + const token0Bal = await token0.balanceOf(signer.address); + const token1Bal = await token1.balanceOf(signer.address); + await token0.connect(signer).approve(router.address, ethers.constants.MaxUint256); + await token1.connect(signer).approve(router.address, ethers.constants.MaxUint256); + await router.connect(signer).addLiquidity(token0.address, token1.address, isStable, token0Bal, token1Bal, 0, 0, signer.address, deadline); + } it("Should withdraw correctly", async () => { + const { want, alice, jar: pickleJar, strategy, controller } = await loadFixture(initialSetupFixture); const _want: BigNumber = await want.balanceOf(alice.address); - await want.approve(pickleJar.address, _want); - await pickleJar.deposit(_want); + await want.connect(alice).approve(pickleJar.address, _want); + await pickleJar.connect(alice).deposit(_want); console.log( "Alice pTokenBalance after deposit: %s\n", (await pickleJar.balanceOf(alice.address)).toString() @@ -76,9 +115,6 @@ export const doTestBehaviorBase = ( await pickleJar.earn(); await increaseTime(60 * 60 * 24 * days); //travel days into the future - if (bIncreaseBlock) { - await increaseBlock((60 * 60 * 24 * days) / bloctime); //roughly days - } console.log( "\nRatio before harvest: ", @@ -127,9 +163,10 @@ export const doTestBehaviorBase = ( }); it("Should harvest correctly", async () => { + const { want, alice, jar: pickleJar, strategy, native, treasury, devfund } = await loadFixture(initialSetupFixture); const _want: BigNumber = await want.balanceOf(alice.address); - await want.approve(pickleJar.address, _want); - await pickleJar.deposit(_want); + await want.connect(alice).approve(pickleJar.address, _want); + await pickleJar.connect(alice).deposit(_want); console.log( "Alice pTokenBalance after deposit: %s\n", (await pickleJar.balanceOf(alice.address)).toString() @@ -137,9 +174,7 @@ export const doTestBehaviorBase = ( await pickleJar.earn(); await increaseTime(60 * 60 * 24 * days); //travel days into the future - if (bIncreaseBlock) { - await increaseBlock((60 * 60 * 24 * days) / bloctime); //roughly days - } + const pendingRewards: [string[], BigNumber[]] = await strategy.getHarvestable(); const _before: BigNumber = await pickleJar.balance(); @@ -217,6 +252,7 @@ export const doTestBehaviorBase = ( }); it("Should perform multiple deposits and withdrawals correctly", async () => { + const { want, alice, jar: pickleJar, strategist } = await loadFixture(initialSetupFixture); const _wantHalved: BigNumber = (await want.balanceOf(alice.address)).div( 2 ); @@ -286,17 +322,18 @@ export const doTestBehaviorBase = ( }); it("should add and remove rewards correctly", async () => { + const { alice, strategy, native } = await loadFixture(initialSetupFixture); const routify = (from: string, to: string, isStable: boolean) => { return { from: from, to: to, stable: isStable }; }; // Addresses - const usdc = "0x7F5c764cBc14f9669B88837ca1490cCa17c31607"; - const notRewardToken = usdc; // Any token address that is not a reward token + const notRewardToken = await strategy.want(); // Any token address that is not a reward token + const reward_addr = await strategy.velo(); // A valid toNative route for a currently registered reward token (can be the same as the registered one) - // it should not add to activeRewardsTokens array! - const validToNativeRoute = [routify(reward_addr, usdc, false)]; + // it should not add to activeRewardsTokens array, only replace the existing array element! + const validToNativeRoute = [routify(reward_addr, notRewardToken, false)]; // Arbitrary new reward route const arbNewRoute = [routify(notRewardToken, native.address, false)]; @@ -332,13 +369,5 @@ export const doTestBehaviorBase = ( "Deactivating reward failed" ); }); - - beforeEach(async () => { - preTestSnapshotID = await network.provider.send("evm_snapshot"); - }); - - afterEach(async () => { - await network.provider.send("evm_revert", [preTestSnapshotID]); - }); }); }; diff --git a/src/tests/strategies/polygon/sushiswap/strategy-polygon-sushi-eth-usdt-lp.test.sol b/src/tests/strategies/polygon/sushiswap/strategy-polygon-sushi-eth-usdt-lp.test.sol deleted file mode 100644 index 4dffbabaf..000000000 --- a/src/tests/strategies/polygon/sushiswap/strategy-polygon-sushi-eth-usdt-lp.test.sol +++ /dev/null @@ -1,72 +0,0 @@ -pragma solidity ^0.6.7; - - - -import "../../../lib/polygon/sushiswap/test-strategy-sushi-farm-base.sol"; - -import "../../../../interfaces/sushi-strategy.sol"; -import "../../../../interfaces/curve.sol"; -import "../../../../interfaces/uniswapv2.sol"; - -import "../../../../pickle-jar.sol"; -import "../../../../controller-v4.sol"; -import "../../../../strategies/polygon/sushiswap/strategy-sushi-eth-usdt-lp.sol"; - -contract StrategySushiEthUsdtLpTest is StrategySushiFarmTestBase { - function setUp() public { - want = 0xc2755915a85C6f6c1C0F3a86ac8C058F11Caa9C9; - - governance = address(this); - strategist = address(this); - devfund = address(new User()); - treasury = address(new User()); - timelock = address(this); - - controller = new ControllerV4( - governance, - strategist, - timelock, - devfund, - treasury - ); - - strategy = ISushiStrategy( - address( - new StrategySushiEthUsdtLp( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - pickleJar = new PickleJar( - strategy.want(), - governance, - timelock, - address(controller) - ); - - controller.setJar(strategy.want(), address(pickleJar)); - controller.approveStrategy(strategy.want(), address(strategy)); - controller.setStrategy(strategy.want(), address(strategy)); - - // Set time - hevm.warp(startTime); - } - - // **** Tests **** - - function test_ethusdtv3_1_timelock() public { - _test_timelock(); - } - - function test_ethusdtv3_1_withdraw_release() public { - _test_withdraw_release(); - } - - function test_ethusdtv3_1_get_earn_harvest_rewards() public { - _test_get_earn_harvest_rewards(); - } -} diff --git a/src/tests/strategies/polygon/sushiswap/strategy-polygon-sushi-matic-eth-lp.test.sol b/src/tests/strategies/polygon/sushiswap/strategy-polygon-sushi-matic-eth-lp.test.sol deleted file mode 100644 index c1348ec8b..000000000 --- a/src/tests/strategies/polygon/sushiswap/strategy-polygon-sushi-matic-eth-lp.test.sol +++ /dev/null @@ -1,70 +0,0 @@ -pragma solidity ^0.6.7; - -import "../../../lib/polygon/sushiswap/test-strategy-sushi-farm-base.sol"; - -import "../../../../interfaces/sushi-strategy.sol"; -import "../../../../interfaces/curve.sol"; -import "../../../../interfaces/uniswapv2.sol"; - -import "../../../../pickle-jar.sol"; -import "../../../../controller-v4.sol"; -import "../../../../strategies/polygon/sushiswap/strategy-sushi-matic-eth-lp.sol"; - -contract StrategySushiMaticEthLpTest is StrategySushiFarmTestBase { - function setUp() public { - want = 0xc4e595acDD7d12feC385E5dA5D43160e8A0bAC0E; - - governance = address(this); - strategist = address(this); - devfund = address(new User()); - treasury = address(new User()); - timelock = address(this); - - controller = new ControllerV4( - governance, - strategist, - timelock, - devfund, - treasury - ); - - strategy = ISushiStrategy( - address( - new StrategySushiMaticEthLp( - governance, - strategist, - address(controller), - timelock - ) - ) - ); - - pickleJar = new PickleJar( - strategy.want(), - governance, - timelock, - address(controller) - ); - - controller.setJar(strategy.want(), address(pickleJar)); - controller.approveStrategy(strategy.want(), address(strategy)); - controller.setStrategy(strategy.want(), address(strategy)); - - // Set time - hevm.warp(startTime); - } - - // **** Tests **** - - function test_maticethv3_1_timelock() public { - _test_timelock(); - } - - function test_maticethv3_1_withdraw_release() public { - _test_withdraw_release(); - } - - function test_maticethv3_1_get_earn_harvest_rewards() public { - _test_get_earn_harvest_rewards(); - } -} diff --git a/src/tests/strategies/polygon/sushiswap/strategy-sushi-matic-usdc-scplp.test.ts b/src/tests/strategies/polygon/sushiswap/strategy-sushi-matic-usdc-scplp.test.ts new file mode 100644 index 000000000..db563595e --- /dev/null +++ b/src/tests/strategies/polygon/sushiswap/strategy-sushi-matic-usdc-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/polygon/sushiswap/strategy-sushi-matic-usdc-scplp.sol:StrategyPolySushiMaticUsdcScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/polygon/sushiswap/strategy-sushi-pickle-dai-lp.test.js b/src/tests/strategies/polygon/sushiswap/strategy-sushi-pickle-dai-lp.test.js deleted file mode 100644 index 5b0191599..000000000 --- a/src/tests/strategies/polygon/sushiswap/strategy-sushi-pickle-dai-lp.test.js +++ /dev/null @@ -1,17 +0,0 @@ -const {toWei} = require("../../../utils/testHelper"); -const {getWantFromWhale} = require("../../../utils/setupHelper"); -const {doTestBehaviorBase} = require("../../testBehaviorBase"); -const {POLYGON_SUSHI_ROUTER} = require("../../../utils/constants"); - - -describe("StrategySushiPickleDaiLp", () => { - const want_addr = "0x57602582eb5e82a197bae4e8b6b80e39abfc94eb"; - const whale_addr = "0x2b23d9b02fffa1f5441ef951b4b95c09faa57eba" - - before("Get want token", async () => { - [alice] = await hre.ethers.getSigners(); - await getWantFromWhale(want_addr, toWei(15), alice, whale_addr); - }); - - doTestBehaviorBase("StrategySushiPickleDaiLp", want_addr); -}); diff --git a/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-dai-sslp.test.ts b/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-dai-sslp.test.ts new file mode 100644 index 000000000..b31d464fc --- /dev/null +++ b/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-dai-sslp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/polygon/sushiswap/strategy-sushi-usdc-dai-sslp.sol:StrategyPolySushiUsdcDaiSslp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-eth-scplp.test.ts b/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-eth-scplp.test.ts new file mode 100644 index 000000000..c473ef3ae --- /dev/null +++ b/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-eth-scplp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/polygon/sushiswap/strategy-sushi-usdc-eth-scplp.sol:StrategyPolySushiUsdcEthScplp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-usdt-sslp.test.ts b/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-usdt-sslp.test.ts new file mode 100644 index 000000000..0afb03f07 --- /dev/null +++ b/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdc-usdt-sslp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/polygon/sushiswap/strategy-sushi-usdc-usdt-sslp.sol:StrategyPolySushiUsdcUsdtSslp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdplus-usdc-sslp.test.ts b/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdplus-usdc-sslp.test.ts new file mode 100644 index 000000000..add2e756a --- /dev/null +++ b/src/tests/strategies/polygon/sushiswap/strategy-sushi-usdplus-usdc-sslp.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "../../sushiswap/strategySushiBase"; + +const contract = "src/strategies/polygon/sushiswap/strategy-sushi-usdplus-usdc-sslp.sol:StrategyPolySushiUsdplusUsdcSslp"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/polygon/univ3/migration.test.ts b/src/tests/strategies/polygon/univ3/migration.test.ts new file mode 100644 index 000000000..9f4e31583 --- /dev/null +++ b/src/tests/strategies/polygon/univ3/migration.test.ts @@ -0,0 +1,440 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { ethers } from "hardhat"; +import { loadFixture, mine, setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { BigNumber, Contract } from "ethers"; +import { deployContract, getContractAt } from "../../../utils/testHelper"; +import { callExecuteToProxy, sendGnosisSafeTxn } from "../../../utils/multisigHelper"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + +const doRebalanceTestWithMigration = async ( + strategyContractName: string, + basStrategyAddress: string, + ethAmountToSwap: number, +) => { + describe("UniV3 Strategy", () => { + const setupFixture = async () => { + const [governance, treasury, alice, bob, charles, fred] = await ethers.getSigners(); + + // Strategy setup + const strategy = await getContractAt(strategyContractName, basStrategyAddress); + + const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + + // Transfer governance on bad strategy + const timelockAddress = await strategy.governance(); + await sendGnosisSafeTxn(timelockAddress, strategy, "setGovernance", [governance.address]); + await sendGnosisSafeTxn(timelockAddress, strategy, "setTimelock", [governance.address]); + + return { strategy, governance, treasury, token0, token1, alice, bob, charles, fred }; + }; + + const setupRebalanceTestFixture = async () => { + const { governance, treasury, token0, token1, strategy: badStrat } = await loadFixture(setupFixture); + + // Controller setup + // const controllerContractName = "/src/optimism/controller-v7.sol:ControllerV7"; + const controllerContractName = "/src/controller-v6.sol:ControllerV6"; + const controller = await deployContract(controllerContractName, + governance.address, + governance.address, + governance.address, + governance.address, + treasury.address + ) + + // Strategy setup + const poolAddr = await badStrat.pool(); + const poolAbi = ["function tickSpacing() view returns(int24)"]; + const pool = await ethers.getContractAt(poolAbi, poolAddr); + const tickSpacing = await pool.tickSpacing(); + const utick = await badStrat.tick_upper(); + const ltick = await badStrat.tick_lower(); + const tickRangeMultiplier = ((utick - ltick) / 2) / tickSpacing; + + const strategyContractFactory = await ethers.getContractFactory(strategyContractName); + const strategy = await strategyContractFactory.deploy( + tickRangeMultiplier, + governance.address, + governance.address, + controller.address, + governance.address + ); + + await controller.connect(governance).approveStrategy(poolAddr, strategy.address); + await controller.connect(governance).setStrategy(poolAddr, strategy.address); + + // Transfer Liquidity position from the bad strategy to the new one + const tokenId = await badStrat.tokenId(); + const nftManAddr = await badStrat.nftManager(); + const nftManAbi = [ + "function transferFrom(address from, address to, uint256 tokenId)", + "function ownerOf(uint256) view returns(address)", + ]; + const nftManContract = await ethers.getContractAt(nftManAbi, nftManAddr); + + await callExecuteToProxy(governance, badStrat, nftManContract, "transferFrom", [ + badStrat.address, + strategy.address, + tokenId, + ]); + + // transfer badstrat remaining usdt to the new strategy + const badStratBal0: BigNumber = await token0.balanceOf(badStrat.address); + const badStratBal1: BigNumber = await token1.balanceOf(badStrat.address); + if (!badStratBal0.isZero()) { + await callExecuteToProxy(governance, badStrat, token0, "transfer", [strategy.address, badStratBal0]) + } + if (!badStratBal1.isZero()) { + await callExecuteToProxy(governance, badStrat, token1, "transfer", [strategy.address, badStratBal1]) + } + + return { strategy, governance, token0, token1 }; + }; + const setupMigrationFixture = async () => { + const { governance, treasury, token0, token1, strategy: badStrat, alice, bob, charles, fred } = await loadFixture(setupFixture); + + // Controller setup + // const controllerContractName = "/src/optimism/controller-v7.sol:ControllerV7"; + const controllerContractName = "/src/controller-v6.sol:ControllerV6"; + const controllerAddr = await badStrat.controller(); + const controller = await getContractAt(controllerContractName, controllerAddr); + const multiSigAddr = await controller.timelock(); + await sendGnosisSafeTxn(multiSigAddr, controller, "setGovernance", [governance.address]); + await sendGnosisSafeTxn(multiSigAddr, controller, "setTimelock", [governance.address]); + await controller.connect(governance).setStrategist(governance.address); + await controller.connect(governance).setTreasury(treasury.address); + + // Jar setup + const poolAddr = await badStrat.pool(); + const jarContract = "src/polygon/pickle-jar-univ3.sol:PickleJarUniV3Poly"; + const jarAddr = await controller.jars(poolAddr); + const jar = await getContractAt(jarContract, jarAddr); + + // Strategy setup + const poolAbi = ["function tickSpacing() view returns(int24)"]; + const pool = await ethers.getContractAt(poolAbi, poolAddr); + const tickSpacing = await pool.tickSpacing(); + const utick = await badStrat.tick_upper(); + const ltick = await badStrat.tick_lower(); + const tickRangeMultiplier = ((utick - ltick) / 2) / tickSpacing; + + const strategyContractFactory = await ethers.getContractFactory(strategyContractName); + const strategy = await strategyContractFactory.deploy( + tickRangeMultiplier, + governance.address, + governance.address, + controller.address, + governance.address + ); + + const native = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.native()); + + // Initial NFT creation + // UniV3 router + const routerAbi = [ + "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) payable returns(uint256 amountOut)", + "function exactInput((bytes path,address recipient,uint256 amountIn,uint256 amountOutMinimum)) payable returns (uint256 amountOut)", + "function WETH9() view returns(address)", + "function factory() view returns(address)", + ]; + const routerAddr = strategy.univ3Router(); + const router = await ethers.getContractAt(routerAbi, routerAddr); + await setBalance(governance.address, ethers.utils.parseEther(ethAmountToSwap.toString())); + await buyToken(governance, ethers.utils.parseEther((ethAmountToSwap / 5).toFixed(0)), token0, router); + await buyToken(governance, ethers.utils.parseEther((ethAmountToSwap / 5).toFixed(0)), token1, router); + await token0.connect(governance).transfer(strategy.address, await token0.balanceOf(governance.address)); + await token1.connect(governance).transfer(strategy.address, await token1.balanceOf(governance.address)); + await strategy.connect(governance).rebalance(); + + // Migrate liquidity + await controller.connect(governance).approveStrategy(poolAddr, strategy.address); + await controller.connect(governance).setStrategy(poolAddr, strategy.address); + await jar.earn(); + await strategy.connect(governance).rebalance(); + + // Set users' ETH balances so they can deposit in jars + const wethTopupAmount = ethers.utils.parseEther((ethAmountToSwap * 2 + 10).toString()); + await setBalance(alice.address, wethTopupAmount); + await setBalance(bob.address, wethTopupAmount); + await setBalance(charles.address, wethTopupAmount); + await setBalance(fred.address, wethTopupAmount); + + // Fill users' token0/token1 balances + await buyToken(alice, ethers.utils.parseEther(ethAmountToSwap.toString()), token0, router); + await buyToken(bob, ethers.utils.parseEther(ethAmountToSwap.toString()), token0, router); + await buyToken(charles, ethers.utils.parseEther(ethAmountToSwap.toString()), token0, router); + await buyToken(fred, ethers.utils.parseEther(ethAmountToSwap.toString()), token0, router); + await buyToken(alice, ethers.utils.parseEther(ethAmountToSwap.toString()), token1, router); + await buyToken(bob, ethers.utils.parseEther(ethAmountToSwap.toString()), token1, router); + await buyToken(charles, ethers.utils.parseEther(ethAmountToSwap.toString()), token1, router); + await buyToken(fred, ethers.utils.parseEther(ethAmountToSwap.toString()), token1, router); + + return { strategy, governance, treasury, jar, controller, token0, token1, native, alice, bob, charles, fred, router, tickRangeMultiplier }; + } + + + describe("Strategy Rebalance", () => { + it("test rebalance on bad strategy", async () => { + const blockNumberBefore = await (await ethers.getSigners().then((x) => x[0])).provider?.getBlockNumber(); + const { governance, strategy, token0, token1 } = await loadFixture(setupFixture); + + const token0name: string = await token0.symbol(); + const token1name: string = await token1.symbol(); + const token0decimals: number = await token0.decimals(); + const token1decimals: number = await token1.decimals(); + + const token0balBefore: BigNumber = await token0.balanceOf(strategy.address); + const token1balBefore: BigNumber = await token1.balanceOf(strategy.address); + const liquidityBefore: BigNumber = await strategy.liquidityOfPool(); + + await strategy.connect(governance).rebalance(); + + const token0balAfter: BigNumber = await token0.balanceOf(strategy.address); + const token1balAfter: BigNumber = await token1.balanceOf(strategy.address); + const liquidityAfter: BigNumber = await strategy.liquidityOfPool(); + + const blockNumberAfter = await governance.provider.getBlockNumber(); + + console.log("\n=== Before Rebalance ==="); + console.log("Block Number Before: " + blockNumberBefore); + console.log(token0name, "balance:", bigToNumber(token0balBefore, token0decimals)); + console.log(token1name, "balance:", bigToNumber(token1balBefore, token1decimals)); + console.log("Liquidity:", bigToNumber(liquidityBefore, 0)); + + console.log("\n=== After Rebalance ==="); + console.log("Block Number After: " + blockNumberAfter); + console.log(token0name, "balance:", bigToNumber(token0balAfter, token0decimals)); + console.log(token1name, "balance:", bigToNumber(token1balAfter, token1decimals)); + console.log("Liquidity:", bigToNumber(liquidityAfter, 0)); + }); + it("test rebalance on fixed strategy", async () => { + const blockNumberBefore = await (await ethers.getSigners().then((x) => x[0])).provider?.getBlockNumber(); + const { governance, strategy, token0, token1 } = await loadFixture(setupRebalanceTestFixture); + + const token0name: string = await token0.symbol(); + const token1name: string = await token1.symbol(); + const token0decimals: number = await token0.decimals(); + const token1decimals: number = await token1.decimals(); + + const token0balBefore: BigNumber = await token0.balanceOf(strategy.address); + const token1balBefore: BigNumber = await token1.balanceOf(strategy.address); + const liquidityBefore: BigNumber = await strategy.liquidityOfPool(); + + await strategy.connect(governance).rebalance(); + + const token0balAfter: BigNumber = await token0.balanceOf(strategy.address); + const token1balAfter: BigNumber = await token1.balanceOf(strategy.address); + const liquidityAfter: BigNumber = await strategy.liquidityOfPool(); + + const blockNumberAfter = await governance.provider.getBlockNumber(); + + console.log("\n=== Before Rebalance ==="); + console.log("Block Number Before: " + blockNumberBefore); + console.log(token0name, "balance:", bigToNumber(token0balBefore, token0decimals)); + console.log(token1name, "balance:", bigToNumber(token1balBefore, token1decimals)); + console.log("Liquidity:", bigToNumber(liquidityBefore, 0)); + + console.log("\n=== After Rebalance ==="); + console.log("Block Number After: " + blockNumberAfter); + console.log(token0name, "balance:", bigToNumber(token0balAfter, token0decimals)); + console.log(token1name, "balance:", bigToNumber(token1balAfter, token1decimals)); + console.log("Liquidity:", bigToNumber(liquidityAfter, 0)); + }); + it.only("Should perform deposits and withdrawals correctly", async () => { + const { governance, treasury, strategy, controller, jar, token0, token1, native, alice, bob, charles, tickRangeMultiplier } = await loadFixture(setupMigrationFixture); + + const token0Decimals = await token0.decimals(); + const token1Decimals = await token1.decimals(); + + // Test setTokenToNative + // const wethAddr = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619"; + // const poolFee = 500; + // const wmaticAddr = "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270" + // const pathEncoded = ethers.utils.solidityPack(["address", "uint24", "address"], [wethAddr, poolFee, wmaticAddr]); + // await strategy.connect(governance).setTokenToNativeRoute(wethAddr,pathEncoded); + + console.log("Jar token0 balance before test => ", bigToNumber(await token0.balanceOf(jar.address), token0Decimals)); + console.log("Jar token1 balance before test => ", bigToNumber(await token1.balanceOf(jar.address), token1Decimals)); + console.log("Strategy token0 balance before test => ", bigToNumber(await token0.balanceOf(strategy.address), token0Decimals)); + console.log("Strategy token1 balance before test => ", bigToNumber(await token1.balanceOf(strategy.address), token1Decimals)); + + // Alice deposit + console.log("===============Alice Deposit=============="); + console.log("Alice token0 balance before deposit => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance before deposit => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + await depositIntoJar(alice, jar, token0, token1); + console.log("Alice token0 balance after deposit => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance after deposit => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + await strategy.connect(governance).setTickRangeMultiplier(Math.floor(tickRangeMultiplier / 2)); + console.log("Rebalancing..."); + await strategy.connect(governance).rebalance(); + + // Bob & Charles deposits + console.log("===============Bob Deposit=============="); + console.log("Bob token0 balance before deposit => ", bigToNumber(await token0.balanceOf(bob.address), token0Decimals)); + console.log("Bob token1 balance before deposit => ", bigToNumber(await token1.balanceOf(bob.address), token1Decimals)); + await depositIntoJar(bob, jar, token0, token1); + console.log("Bob token0 balance after deposit => ", bigToNumber(await token0.balanceOf(bob.address), token0Decimals)); + console.log("Bob token1 balance after deposit => ", bigToNumber(await token1.balanceOf(bob.address), token1Decimals)); + + console.log("===============Charles Deposit=============="); + console.log("Charles token0 balance before deposit => ", bigToNumber(await token0.balanceOf(charles.address), token0Decimals)); + console.log("Charles token1 balance before deposit => ", bigToNumber(await token1.balanceOf(charles.address), token1Decimals)); + await depositIntoJar(charles, jar, token0, token1); + console.log("Charles token0 balance after deposit => ", bigToNumber(await token0.balanceOf(charles.address), token0Decimals)); + console.log("Charles token1 balance after deposit => ", bigToNumber(await token1.balanceOf(charles.address), token1Decimals)); + + const aliceShare = await jar.balanceOf(alice.address); + const bobShare = await jar.balanceOf(bob.address); + const charlesShare = await jar.balanceOf(charles.address); + console.log("Alice share amount => ", bigToNumber(aliceShare)); + console.log("Bob share amount => ", bigToNumber(bobShare)); + console.log("Charles share amount => ", bigToNumber(charlesShare)); + + console.log("===============Alice partial withdraw=============="); + console.log("Alice token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + await jar.connect(alice).withdraw(aliceShare.div(BigNumber.from(2))); + console.log("Alice token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + console.log("Alice shares remaining => ", bigToNumber(await jar.balanceOf(alice.address))); + + await mine(60 * 60 * 24); + + console.log("===============Bob withdraw=============="); + console.log("Bob token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(bob.address), token0Decimals)); + console.log("Bob token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(bob.address), token1Decimals)); + await jar.connect(bob).withdrawAll(); + console.log("Bob token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(bob.address), token0Decimals)); + console.log("Bob token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(bob.address), token1Decimals)); + + console.log("Rebalancing..."); + await strategy.connect(governance).rebalance(); + + console.log("=============== Controller withdraw ==============="); + console.log("Jar token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(jar.address), token0Decimals)); + console.log("Jar token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(jar.address), token1Decimals)); + const poolAddr = strategy.pool(); + await controller.withdrawAll(poolAddr); + console.log("Jar token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(jar.address), token0Decimals)); + console.log("Jar token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(jar.address), token1Decimals)); + + console.log("===============Alice Full withdraw=============="); + console.log("Alice token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + await jar.connect(alice).withdrawAll(); + console.log("Alice token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(alice.address), token0Decimals)); + console.log("Alice token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(alice.address), token1Decimals)); + + console.log("=============== charles withdraw =============="); + console.log("Charles token0 balance before withdrawal => ", bigToNumber(await token0.balanceOf(charles.address), token0Decimals)); + console.log("Charles token1 balance before withdrawal => ", bigToNumber(await token1.balanceOf(charles.address), token1Decimals)); + await jar.connect(charles).withdrawAll(); + console.log("Charles token0 balance after withdrawal => ", bigToNumber(await token0.balanceOf(charles.address), token0Decimals)); + console.log("Charles token1 balance after withdrawal => ", bigToNumber(await token1.balanceOf(charles.address), token1Decimals)); + + console.log("------------------ Finished -----------------------"); + + console.log("Treasury token0 balance => ", bigToNumber(await token0.balanceOf(treasury.address), token0Decimals)); + console.log("Treasury token1 balance => ", bigToNumber(await token1.balanceOf(treasury.address), token1Decimals)); + console.log("Treasury native balance => ", bigToNumber(await native.balanceOf(treasury.address))); + console.log("Strategy token0 balance => ", bigToNumber(await token0.balanceOf(strategy.address), token0Decimals)); + console.log("Strategy token1 balance => ", bigToNumber(await token1.balanceOf(strategy.address), token1Decimals)); + console.log("Strategy native balance => ", bigToNumber(await native.balanceOf(strategy.address))); + console.log("Jar token0 balance => ", bigToNumber(await token0.balanceOf(jar.address), token0Decimals)); + console.log("Jar token1 balance => ", bigToNumber(await token1.balanceOf(jar.address), token1Decimals)); + console.log("Jar native balance => ", bigToNumber(await native.balanceOf(jar.address))); + }); + }); + }); + + const bigToNumber = (amount: BigNumber, decimals = 18) => parseFloat(ethers.utils.formatUnits(amount, decimals)); + + const buyToken = async (buyer: SignerWithAddress, ethAmountToSwap: BigNumber, token: Contract, router: Contract) => { + // Convert ETH to WETH + const wethAddr = await router.WETH9(); + const wethDepositAbi = ["function deposit() payable", "function approve(address,uint256) returns(bool)"]; + const weth = await ethers.getContractAt(wethDepositAbi, wethAddr); + await weth.connect(buyer).deposit({ value: ethAmountToSwap }); + + if (wethAddr === token.address) return; + + // Find the best pool for the swap + const factoryAbi = ["function getPool(address token0, address token1, uint24 fee) view returns(address)"]; + const factoryAddr = await router.factory(); + const factory = await ethers.getContractAt(factoryAbi, factoryAddr); + const fees = [100, 500, 3000]; + let bestPoolBal: BigNumber = BigNumber.from("0"); + let bestPoolFee: number; + for (let i = 0; i < fees.length; i++) { + const fee = fees[i]; + const poolAddr = await factory.getPool(wethAddr, token.address, fee); + if (poolAddr === ethers.constants.AddressZero) continue; + const poolBalance: BigNumber = await token.balanceOf(poolAddr); + if (poolBalance.gt(bestPoolBal)) { + bestPoolBal = poolBalance; + bestPoolFee = fee; + } + } + if (!bestPoolFee) throw `No pool found for WETH-${await token.symbol()}`; + + // Swap + const pathEncoded = ethers.utils.solidityPack(["address", "uint24", "address"], [wethAddr, bestPoolFee, token.address]); + const exactInputParams = [pathEncoded, buyer.address, ethAmountToSwap, 0]; + await weth.connect(buyer).approve(router.address, ethers.constants.MaxUint256); + await router.connect(buyer).exactInput(exactInputParams); + } + + const depositIntoJar = async (user: SignerWithAddress, jar: Contract, token0: Contract, token1: Contract) => { + const token0Bal = await token0.balanceOf(user.address); + const token1Bal = await token1.balanceOf(user.address); + await token0.connect(user).approve(jar.address, token0Bal); + await token1.connect(user).approve(jar.address, token1Bal); + await jar.connect(user).deposit(token0Bal, token1Bal); + }; + +}; + + +// Polygon USDC-USDT (Block 35684316 | tokenId 470244) +// https://polygonscan.com/tx/0x22b7587ce6ab8805faced50970d5e07bdb1d6dd3e6c5ab3dfa72155abdf8f4b0 +// doRebalanceTestWithMigration( +// "src/strategies/polygon/uniswapv3/strategy-univ3-usdc-usdt-lp.sol:StrategyUsdcUsdtUniV3Poly", +// "0x5BEF03597A205F54DB1769424008aEC11e8b0dCB", +// 50, +// ) + +// Polygon WBTC-WETH (Block 34941607 | tokenId 415652) +// https://polygonscan.com/tx/0xccd931261f139f8133ebb98af9423bfef96236523a798170ac8843fadc18e477 +doRebalanceTestWithMigration( + "src/strategies/polygon/uniswapv3/strategy-univ3-wbtc-eth-lp.sol:StrategyWbtcEthUniV3Poly", + "0x3Bae730889B69D049e375431F80c61EF7A198296", + 50, +) + +// Polygon WMATIC-USDC (Block 35210387 | tokenId 416046) +// https://polygonscan.com/tx/0xa15c1928aa5d3f283020d8ce9d03ff2d64119241e855030d4c1c97004869165f +// doRebalanceTestWithMigration( +// "src/strategies/polygon/uniswapv3/strategy-univ3-matic-usdc-lp.sol:StrategyMaticUsdcUniV3Poly", +// "0xf7Cd34FB978277D220E22570A1F208294f98AF92", +// 50, +// ) + +// NOTE: THIS ONE PERFORMED SLIGHTLY WORSE WITH THE NEW IMPLEMENTATION +// Polygon WMATIC-WETH (Block 35318702 | tokenId 392383) +// https://polygonscan.com/tx/0xe85a9b1975609dc73129d86aee44cd3e31776daad8e9db9f153e924483204533 +// doRebalanceTestWithMigration( +// "src/strategies/polygon/uniswapv3/strategy-univ3-matic-eth-lp.sol:StrategyMaticEthUniV3Poly", +// "0x1C170D888D71aC85732609Bd8470D3BBe8e632A7", +// 50, +// ) + +// Polygon WETH-USDC (Block 35366299 | tokenId 429020) +// https://polygonscan.com/tx/0x48b2b00657dd56c30f80dbf528e6b0abd5d2799881809bda1d9b40a87b1e802d +// doRebalanceTestWithMigration( +// "src/strategies/polygon/uniswapv3/strategy-univ3-usdc-eth-lp.sol:StrategyUsdcEthUniV3Poly", +// "0xcDF83A6878C50AD403dF0D68F229696a70972bEf", +// 50, +// ) + diff --git a/src/tests/strategies/polygon/univ3/migrations/btc-eth.test.ts b/src/tests/strategies/polygon/univ3/migrations/btc-eth.test.ts new file mode 100644 index 000000000..cebb0fc73 --- /dev/null +++ b/src/tests/strategies/polygon/univ3/migrations/btc-eth.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyWbtcEthUniV3Poly Migration Test", () => { + const newStrategyContractName = + "src/strategies/polygon/uniswapv3/strategy-univ3-wbtc-eth-lp.sol:StrategyWbtcEthUniV3Poly"; + const oldStrategy = "0xbE27C2415497f8ae5E6103044f460991E32636F8"; + const nativeAmountToDeposit = 100; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); diff --git a/src/tests/strategies/polygon/univ3/migrations/matic-eth.test.ts b/src/tests/strategies/polygon/univ3/migrations/matic-eth.test.ts new file mode 100644 index 000000000..45ced73df --- /dev/null +++ b/src/tests/strategies/polygon/univ3/migrations/matic-eth.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyMaticEthUniV3Poly Migration Test", () => { + const newStrategyContractName = + "src/strategies/polygon/uniswapv3/strategy-univ3-matic-eth-lp.sol:StrategyMaticEthUniV3Poly"; + const oldStrategy = "0x11b8c80F452e54ae3AB2E8ce9eF9603B0a0f56D9"; + const nativeAmountToDeposit = 100; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); diff --git a/src/tests/strategies/polygon/univ3/migrations/matic-usdc.test.ts b/src/tests/strategies/polygon/univ3/migrations/matic-usdc.test.ts new file mode 100644 index 000000000..c95f7029e --- /dev/null +++ b/src/tests/strategies/polygon/univ3/migrations/matic-usdc.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyMaticUsdcUniV3Poly Migration Test", () => { + const newStrategyContractName = + "src/strategies/polygon/uniswapv3/strategy-univ3-matic-usdc-lp.sol:StrategyMaticUsdcUniV3Poly"; + const oldStrategy = "0x293731CA8Da0cf1d6dfFB5125943F05Fe0B5fF99"; + const nativeAmountToDeposit = 100; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); diff --git a/src/tests/strategies/polygon/univ3/migrations/usdc-eth.test.ts b/src/tests/strategies/polygon/univ3/migrations/usdc-eth.test.ts new file mode 100644 index 000000000..cc3daffff --- /dev/null +++ b/src/tests/strategies/polygon/univ3/migrations/usdc-eth.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyUsdcEthUniV3Poly Migration Test", () => { + const newStrategyContractName = + "src/strategies/polygon/uniswapv3/strategy-univ3-usdc-eth-lp.sol:StrategyUsdcEthUniV3Poly"; + const oldStrategy = "0xD5236f71580E951010E814118075F2Dda90254db"; + const nativeAmountToDeposit = 100; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); diff --git a/src/tests/strategies/polygon/univ3/migrations/usdc-usdt.test.ts b/src/tests/strategies/polygon/univ3/migrations/usdc-usdt.test.ts new file mode 100644 index 000000000..9f1c0c013 --- /dev/null +++ b/src/tests/strategies/polygon/univ3/migrations/usdc-usdt.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../../../uniswapv3/jar-migration.test"; + +describe("StrategyUsdcUsdtUniV3Poly Migration Test", () => { + const newStrategyContractName = + "src/strategies/polygon/uniswapv3/strategy-univ3-usdc-usdt-lp.sol:StrategyUsdcUsdtUniV3Poly"; + const oldStrategy = "0x846d0ED75c285E6D70A925e37581D0bFf94c7651"; + const nativeAmountToDeposit = 100; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); diff --git a/src/tests/strategies/saddle/StrategySaddleD4.test.js b/src/tests/strategies/saddle/StrategySaddleD4.test.js deleted file mode 100644 index dcc50f51b..000000000 --- a/src/tests/strategies/saddle/StrategySaddleD4.test.js +++ /dev/null @@ -1,141 +0,0 @@ -const { - expect, - increaseTime, - deployContract, - getContractAt, - unlockAccount, - toWei, - NULL_ADDRESS, -} = require("../../utils/testHelper"); - -describe("StrategySaddleD4 Test", () => { - let alice; - let strategy, pickleJar, controller; - let governance, strategist, devfund, treasury, timelock; - let preTestSnapshotID; - let want; - const want_addr = "0xd48cF4D7FB0824CC8bAe055dF3092584d0a1726A"; - const want_amount = toWei(1000); - - before("Deploy contracts", async () => { - [alice, devfund, treasury] = await hre.ethers.getSigners(); - governance = alice; - strategist = alice; - timelock = alice; - - controller = await deployContract( - "src/polygon/controller-v4.sol:ControllerV4", - governance.address, - strategist.address, - timelock.address, - devfund.address, - treasury.address - ); - console.log("Controller is deployed at ", controller.address); - - strategy = await deployContract( - "StrategySaddleD4v2", - governance.address, - strategist.address, - controller.address, - timelock.address - ); - console.log("Strategy is deployed at ", strategy.address); - - want = await getContractAt("src/lib/erc20.sol:ERC20", want_addr); - - pickleJar = await deployContract( - "PickleJar", - want.address, - governance.address, - timelock.address, - controller.address - ); - console.log("PickleJar is deployed at ", pickleJar.address); - - await controller.setJar(want.address, pickleJar.address); - await controller.approveStrategy(want.address, strategy.address); - await controller.setStrategy(want.address, strategy.address); - // get want token - await getWant(); - }); - - it("Should withdraw correctly", async () => { - const _want = await want.balanceOf(alice.address); - await want.approve(pickleJar.address, _want); - await pickleJar.deposit(_want); - await pickleJar.earn(); - - await increaseTime(60 * 60 * 24 * 1); - console.log("Ratio before harvest: ", (await pickleJar.getRatio()).toString()); - await strategy.harvest(); - console.log("Ratio after harvest: ", (await pickleJar.getRatio()).toString()); - - await increaseTime(60 * 60 * 24 * 1); - let _before = await want.balanceOf(pickleJar.address); - await controller.withdrawAll(want.address); - let _after = await want.balanceOf(pickleJar.address); - expect(_after).to.be.gt(_before, "controller withdrawAll failed"); - - _before = await want.balanceOf(alice.address); - await pickleJar.withdrawAll(); - _after = await want.balanceOf(alice.address); - - expect(_after).to.be.gt(_before, "picklejar withdrawAll failed"); - expect(_after).to.be.gt(_want, "no interest earned"); - }); - - it("Should harvest correctly", async () => { - const _want = await want.balanceOf(alice.address); - await want.approve(pickleJar.address, _want); - await pickleJar.deposit(_want); - await pickleJar.earn(); - await increaseTime(60 * 60 * 24 * 1); - - const _before = await pickleJar.balance(); - let _treasuryBefore = await want.balanceOf(treasury.address); - console.log("Ratio before harvest: ", (await pickleJar.getRatio()).toString()); - await strategy.harvest(); - console.log("Ratio after harvest: ", (await pickleJar.getRatio()).toString()); - const _after = await pickleJar.balance(); - let _treasuryAfter = await want.balanceOf(treasury.address); - - await increaseTime(60 * 60 * 24 * 1); - //20% performance fee is given - const earned = _after.sub(_before).mul(1000).div(800); - const earnedRewards = earned.mul(200).div(1000); - const actualRewardsEarned = _treasuryAfter.sub(_treasuryBefore); - - expect(earnedRewards).to.be.eqApprox(actualRewardsEarned, "20% performance fee is not given"); - - //withdraw - const _devBefore = await want.balanceOf(devfund.address); - _treasuryBefore = await want.balanceOf(treasury.address); - await pickleJar.withdrawAll(); - const _devAfter = await want.balanceOf(devfund.address); - _treasuryAfter = await want.balanceOf(treasury.address); - - //0% goes to dev - const _devFund = _devAfter.sub(_devBefore); - expect(_devFund).to.be.eq(0, "dev've stolen money!!!!!"); - - //0% goes to treasury - const _treasuryFund = _treasuryAfter.sub(_treasuryBefore); - expect(_treasuryFund).to.be.eq(0, "treasury've stolen money!!!!"); - }); - - const getWant = async () => { - const whale = await unlockAccount("0xDF9511D05D585Fc5f5Cf572Ebd98e3398314516E"); - await want.connect(whale).transfer(alice.address, want_amount); - const _balance = await want.balanceOf(alice.address); - expect(_balance).to.be.eq(want_amount, "get want failed"); - }; - - beforeEach(async () => { - preTestSnapshotID = await hre.network.provider.send("evm_snapshot"); - }); - - afterEach(async () => { - await hre.network.provider.send("evm_revert", [preTestSnapshotID]); - }); -}); diff --git a/src/tests/strategies/saddle/strategy-saddle-d4.test.ts b/src/tests/strategies/saddle/strategy-saddle-d4.test.ts new file mode 100644 index 000000000..2f156cc99 --- /dev/null +++ b/src/tests/strategies/saddle/strategy-saddle-d4.test.ts @@ -0,0 +1,6 @@ +import { doTestBehaviorBase } from "./strategySaddleBase"; + +const contract = "src/strategies/saddle/strategy-saddle-d4-v3.sol:StrategySaddleD4"; +const name = contract.substring(contract.lastIndexOf(":") + 1); + +describe(name, () => doTestBehaviorBase(contract, 6, 50)); diff --git a/src/tests/strategies/saddle/strategySaddleBase.ts b/src/tests/strategies/saddle/strategySaddleBase.ts new file mode 100644 index 000000000..8c233be0a --- /dev/null +++ b/src/tests/strategies/saddle/strategySaddleBase.ts @@ -0,0 +1,225 @@ +import "@nomicfoundation/hardhat-toolbox"; +import {ethers} from "hardhat"; +import {expect, increaseTime, getContractAt, unlockAccount} from "../../utils/testHelper"; +import {setup} from "../../utils/setupHelper"; +import {BigNumber, Contract} from "ethers"; +import {loadFixture, setBalance} from "@nomicfoundation/hardhat-network-helpers"; +import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; + +export const doTestBehaviorBase = (strategyName: string, days = 15, wantSize = 1) => { + const shortName = strategyName.substring(strategyName.lastIndexOf(":") + 1); + describe(`${shortName} common behavior tests`, () => { + const initialSetupFixture = async () => { + const [governance, treasury, devfund] = await ethers.getSigners(); + + const alice = governance; + const strategist = devfund; + const timelock = alice; + + const [controller, strategy, jar] = await setup( + strategyName, + governance, + strategist, + timelock, + devfund, + treasury + ); + + // await strategy.init(); + + const want = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.want()); + // const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + // const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + const native = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.native()); + + // Get some want tokens into Alice wallet + await setBalance(alice.address, ethers.utils.parseEther((wantSize * 2).toString())); + const wNativeDepositAbi = ["function deposit() payable", "function approve(address,uint256) returns(bool)"]; + const wNativeDeposit = await ethers.getContractAt(wNativeDepositAbi, native.address); + await wNativeDeposit.connect(alice).deposit({value: ethers.utils.parseEther(wantSize.toString())}); + await getWantFor(alice, strategy); + const wantBalance: BigNumber = await want.balanceOf(alice.address); + expect(wantBalance.isZero()).to.be.eq(false, "Alice failed to get some want tokens!"); + + return { + strategy, + jar, + controller, + governance, + treasury, + timelock, + devfund, + strategist, + alice, + want, + native, + // token0, + // token1, + }; + }; + + it("Should set the timelock correctly", async () => { + const {strategy, timelock, devfund} = await loadFixture(initialSetupFixture); + expect(await strategy.timelock()).to.be.eq(timelock.address, "timelock is incorrect"); + await strategy.connect(timelock).setPendingTimelock(devfund.address); + await strategy.connect(devfund).acceptTimelock(); + expect(await strategy.timelock()).to.be.eq(devfund.address, "timelock is incorrect"); + }); + + it("Should withdraw correctly", async () => { + const {want, alice, jar: pickleJar, strategy, controller} = await loadFixture(initialSetupFixture); + const _want: BigNumber = await want.balanceOf(alice.address); + await want.connect(alice).approve(pickleJar.address, _want); + await pickleJar.connect(alice).deposit(_want); + console.log("Alice pTokenBalance after deposit: %s\n", (await pickleJar.balanceOf(alice.address)).toString()); + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * days); //travel days into the future + + console.log("\nRatio before harvest: ", (await pickleJar.getRatio()).toString()); + + await strategy.harvest(); + + console.log("Ratio after harvest: %s", (await pickleJar.getRatio()).toString()); + + let _before: BigNumber = await want.balanceOf(pickleJar.address); + console.log("\nPicklejar balance before controller withdrawal: ", _before.toString()); + + await controller.withdrawAll(want.address); + + let _after: BigNumber = await want.balanceOf(pickleJar.address); + console.log("Picklejar balance after controller withdrawal: ", _after.toString()); + + expect(_after).to.be.gt(_before, "controller withdrawAll failed"); + + _before = await want.balanceOf(alice.address); + console.log("\nAlice balance before picklejar withdrawal: ", _before.toString()); + + await pickleJar.withdrawAll(); + + _after = await want.balanceOf(alice.address); + console.log("Alice balance after picklejar withdrawal: ", _after.toString()); + + expect(_after).to.be.gt(_before, "picklejar withdrawAll failed"); + expect(_after).to.be.gt(_want, "no interest earned"); + }); + + it("Should harvest correctly", async () => { + const {want, alice, jar: pickleJar, strategy, native, treasury, devfund} = await loadFixture(initialSetupFixture); + const _want: BigNumber = await want.balanceOf(alice.address); + await want.connect(alice).approve(pickleJar.address, _want); + await pickleJar.connect(alice).deposit(_want); + console.log("Alice pTokenBalance after deposit: %s\n", (await pickleJar.balanceOf(alice.address)).toString()); + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * days); //travel days into the future + + const pendingRewards: [string[], BigNumber[]] = await strategy.getHarvestable(); + const _before: BigNumber = await pickleJar.balance(); + let _treasuryBefore: BigNumber = await native.balanceOf(treasury.address); + + console.log("Rewards harvestable amounts:"); + pendingRewards[0].forEach((addr, i) => { + console.log(`\t${addr}: ${pendingRewards[1][i].toString()}`); + }); + console.log("Picklejar balance before harvest: ", _before.toString()); + console.log("💸 Treasury reward token balance before harvest: ", _treasuryBefore.toString()); + console.log("\nRatio before harvest: ", (await pickleJar.getRatio()).toString()); + + await strategy.harvest(); + + const _after: BigNumber = await pickleJar.balance(); + let _treasuryAfter: BigNumber = await native.balanceOf(treasury.address); + console.log("Ratio after harvest: ", (await pickleJar.getRatio()).toString()); + console.log("\nPicklejar balance after harvest: ", _after.toString()); + console.log("💸 Treasury reward token balance after harvest: ", _treasuryAfter.toString()); + + //performance fee is given + const rewardsEarned = _treasuryAfter.sub(_treasuryBefore); + console.log("\nPerformance fee earned by treasury: ", rewardsEarned.toString()); + + expect(rewardsEarned).to.be.gt(0, "no performance fee taken"); + + //withdraw + const _devBefore: BigNumber = await want.balanceOf(devfund.address); + _treasuryBefore = await want.balanceOf(treasury.address); + console.log("\n👨‍🌾 Dev balance before picklejar withdrawal: ", _devBefore.toString()); + console.log("💸 Treasury balance before picklejar withdrawal: ", _treasuryBefore.toString()); + + await pickleJar.withdrawAll(); + + const _devAfter: BigNumber = await want.balanceOf(devfund.address); + _treasuryAfter = await want.balanceOf(treasury.address); + console.log("\n👨‍🌾 Dev balance after picklejar withdrawal: ", _devAfter.toString()); + console.log("💸 Treasury balance after picklejar withdrawal: ", _treasuryAfter.toString()); + + //0% goes to dev + const _devFund = _devAfter.sub(_devBefore); + expect(_devFund).to.be.eq(0, "dev've stolen money!!!!!"); + + //0% goes to treasury + const _treasuryFund = _treasuryAfter.sub(_treasuryBefore); + expect(_treasuryFund).to.be.eq(0, "treasury've stolen money!!!!"); + }); + + it("Should perform multiple deposits and withdrawals correctly", async () => { + const {want, alice, jar: pickleJar, strategist} = await loadFixture(initialSetupFixture); + const _wantHalved: BigNumber = (await want.balanceOf(alice.address)).div(2); + await want.connect(alice).transfer(strategist.address, _wantHalved); + await want.connect(alice).approve(pickleJar.address, _wantHalved); + await want.connect(strategist).approve(pickleJar.address, _wantHalved); + + console.log("\nAlice starting balance: %s\n", _wantHalved.toString()); + + await pickleJar.connect(alice).deposit(_wantHalved); + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * 2); //travel 2 days + + await pickleJar.connect(strategist).deposit(_wantHalved); + + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * 2); //travel 2 days + + // Alice withdraws half + await pickleJar.connect(alice).withdraw(_wantHalved.div(2)); + + await pickleJar.earn(); + + // Strategist withdraws all + await pickleJar.connect(strategist).withdrawAll(); + + let _aliceBalanceAfter: BigNumber = await want.balanceOf(alice.address); + let _strategistBalanceAfter: BigNumber = await want.balanceOf(strategist.address); + console.log("\nAlice balance after half withdrawal: %s\n", _aliceBalanceAfter.toString()); + console.log("\nStrategist balance after half withdrawal: %s\n", _strategistBalanceAfter.toString()); + + expect(_aliceBalanceAfter).to.be.approximately(_wantHalved.div(2), 1, "Alice withdrawal amount incorrect"); + + expect(_strategistBalanceAfter).to.be.approximately(_wantHalved, 1, "Strategist withdrawal amount incorrect"); + + // Alice withdraws remainder + + await pickleJar.connect(alice).withdrawAll(); + _aliceBalanceAfter = await want.balanceOf(alice.address); + console.log("\nAlice balance after full withdrawal: %s\n", _aliceBalanceAfter.toString()); + expect(_aliceBalanceAfter).to.be.approximately(_wantHalved, 1, "Alice withdrawal amount incorrect"); + }); + + it("Should add, update and remove rewards correctly", async () => { + // TODO + }); + }); +}; + +// Helpers +const getWantFor = async (signer: SignerWithAddress, strategy: Contract) => { + const want_amount = ethers.utils.parseEther("1000"); + const whale = await unlockAccount("0xDF9511D05D585Fc5f5Cf572Ebd98e3398314516E"); + const want = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.want()); + await setBalance(whale.address, ethers.utils.parseEther("2")); + await want.connect(whale).transfer(signer.address, want_amount); + const _balance = await want.balanceOf(signer.address); + expect(_balance).to.be.eq(want_amount, "get want failed"); +}; diff --git a/src/tests/strategies/saddle/withdrawsaddle.test.js b/src/tests/strategies/saddle/withdrawsaddle.test.js deleted file mode 100644 index e4749cdaa..000000000 --- a/src/tests/strategies/saddle/withdrawsaddle.test.js +++ /dev/null @@ -1,40 +0,0 @@ -const {expect, deployContract, getContractAt, unlockAccount, toWei} = require("../../utils/testHelper"); - -describe("StrategySaddleD4 Test", () => { - const want_addr = "0xd48cF4D7FB0824CC8bAe055dF3092584d0a1726A"; - const want_amount = toWei(1000); - let saddleStrategy; - let want; - let timelock; - let withdrawSaddle; - const SADDLE_STRATEGY_ADDR = "0x4A974495E20A8E0f5ce1De59eB15CfffD19Bcf8d"; - - before("Deploy contracts", async () => { - saddleStrategy = await getContractAt("StrategySaddleD4", SADDLE_STRATEGY_ADDR); - withdrawSaddle = await deployContract("WithdrawSaddle"); - want = await getContractAt("ERC20", want_addr); - timelock = await unlockAccount("0xaCfE4511CE883C14c4eA40563F176C3C09b4c47C"); - }); - - it("should withdraw correctly", async () => { - const prevBalance = await want.balanceOf(SADDLE_STRATEGY_ADDR); - console.log("Prev Balance => ", prevBalance.toString()); - console.log( - "Prev Balance of gov => ", - (await want.balanceOf("0xaCfE4511CE883C14c4eA40563F176C3C09b4c47C")).toString() - ); - await saddleStrategy - .connect(timelock) - .execute( - withdrawSaddle.address, - "0x2e1a7d4d00000000000000000000000000000000000000000000240dd026f3dbd7d00000" - ); //withdrawing 10000 tokens - const afterBalance = await want.balanceOf(SADDLE_STRATEGY_ADDR); - console.log("After Balance => ", afterBalance.toString()); - - console.log( - "After Balance of gov => ", - (await want.balanceOf("0xaCfE4511CE883C14c4eA40563F176C3C09b4c47C")).toString() - ); - }); -}); diff --git a/src/tests/strategies/sushiswap/strategySushiBase.ts b/src/tests/strategies/sushiswap/strategySushiBase.ts new file mode 100644 index 000000000..be01cd0d1 --- /dev/null +++ b/src/tests/strategies/sushiswap/strategySushiBase.ts @@ -0,0 +1,451 @@ +import "@nomicfoundation/hardhat-toolbox"; +import {ethers} from "hardhat"; +import {expect, increaseTime, getContractAt} from "../../utils/testHelper"; +import {setup} from "../../utils/setupHelper"; +import {NULL_ADDRESS} from "../../utils/constants"; +import {BigNumber, Contract} from "ethers"; +import {loadFixture, setBalance} from "@nomicfoundation/hardhat-network-helpers"; +import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; + +export const doTestBehaviorBase = (strategyName: string, days = 15, wantSize = 1) => { + const shortName = strategyName.substring(strategyName.lastIndexOf(":") + 1); + describe(`${shortName} common behavior tests`, () => { + const initialSetupFixture = async () => { + const [governance, treasury, devfund] = await ethers.getSigners(); + + const alice = governance; + const strategist = devfund; + const timelock = alice; + + const [controller, strategy, jar] = await setup( + strategyName, + governance, + strategist, + timelock, + devfund, + treasury + ); + + // await strategy.init(); + + const want = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.want()); + const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + const native = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.native()); + + // Get some want tokens into Alice wallet + await setBalance(alice.address, ethers.utils.parseEther((wantSize * 2).toString())); + const wNativeDepositAbi = ["function deposit() payable", "function approve(address,uint256) returns(bool)"]; + const wNativeDeposit = await ethers.getContractAt(wNativeDepositAbi, native.address); + await wNativeDeposit.connect(alice).deposit({value: ethers.utils.parseEther(wantSize.toString())}); + await getWantFor(alice, strategy); + const wantBalance: BigNumber = await want.balanceOf(alice.address); + expect(wantBalance.isZero()).to.be.eq(false, "Alice failed to get some want tokens!"); + + return { + strategy, + jar, + controller, + governance, + treasury, + timelock, + devfund, + strategist, + alice, + want, + native, + token0, + token1, + }; + }; + + it("Should set the timelock correctly", async () => { + const {strategy, timelock, devfund} = await loadFixture(initialSetupFixture); + expect(await strategy.timelock()).to.be.eq(timelock.address, "timelock is incorrect"); + await strategy.connect(timelock).setPendingTimelock(devfund.address); + await strategy.connect(devfund).acceptTimelock(); + expect(await strategy.timelock()).to.be.eq(devfund.address, "timelock is incorrect"); + }); + + it("Should withdraw correctly", async () => { + const {want, alice, jar: pickleJar, strategy, controller} = await loadFixture(initialSetupFixture); + const _want: BigNumber = await want.balanceOf(alice.address); + await want.connect(alice).approve(pickleJar.address, _want); + await pickleJar.connect(alice).deposit(_want); + console.log("Alice pTokenBalance after deposit: %s\n", (await pickleJar.balanceOf(alice.address)).toString()); + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * days); //travel days into the future + + console.log("\nRatio before harvest: ", (await pickleJar.getRatio()).toString()); + + await strategy.harvest(); + + console.log("Ratio after harvest: %s", (await pickleJar.getRatio()).toString()); + + let _before: BigNumber = await want.balanceOf(pickleJar.address); + console.log("\nPicklejar balance before controller withdrawal: ", _before.toString()); + + await controller.withdrawAll(want.address); + + let _after: BigNumber = await want.balanceOf(pickleJar.address); + console.log("Picklejar balance after controller withdrawal: ", _after.toString()); + + expect(_after).to.be.gt(_before, "controller withdrawAll failed"); + + _before = await want.balanceOf(alice.address); + console.log("\nAlice balance before picklejar withdrawal: ", _before.toString()); + + await pickleJar.withdrawAll(); + + _after = await want.balanceOf(alice.address); + console.log("Alice balance after picklejar withdrawal: ", _after.toString()); + + expect(_after).to.be.gt(_before, "picklejar withdrawAll failed"); + expect(_after).to.be.gt(_want, "no interest earned"); + }); + + it("Should harvest correctly", async () => { + const {want, alice, jar: pickleJar, strategy, native, treasury, devfund} = await loadFixture(initialSetupFixture); + const _want: BigNumber = await want.balanceOf(alice.address); + await want.connect(alice).approve(pickleJar.address, _want); + await pickleJar.connect(alice).deposit(_want); + console.log("Alice pTokenBalance after deposit: %s\n", (await pickleJar.balanceOf(alice.address)).toString()); + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * days); //travel days into the future + + const pendingRewards: [string[], BigNumber[]] = await strategy.getHarvestable(); + const _before: BigNumber = await pickleJar.balance(); + let _treasuryBefore: BigNumber = await native.balanceOf(treasury.address); + + console.log("Rewards harvestable amounts:"); + pendingRewards[0].forEach((addr, i) => { + console.log(`\t${addr}: ${pendingRewards[1][i].toString()}`); + }); + console.log("Picklejar balance before harvest: ", _before.toString()); + console.log("💸 Treasury reward token balance before harvest: ", _treasuryBefore.toString()); + console.log("\nRatio before harvest: ", (await pickleJar.getRatio()).toString()); + + await strategy.harvest(); + + const _after: BigNumber = await pickleJar.balance(); + let _treasuryAfter: BigNumber = await native.balanceOf(treasury.address); + console.log("Ratio after harvest: ", (await pickleJar.getRatio()).toString()); + console.log("\nPicklejar balance after harvest: ", _after.toString()); + console.log("💸 Treasury reward token balance after harvest: ", _treasuryAfter.toString()); + + //performance fee is given + const rewardsEarned = _treasuryAfter.sub(_treasuryBefore); + console.log("\nPerformance fee earned by treasury: ", rewardsEarned.toString()); + + expect(rewardsEarned).to.be.gt(0, "no performance fee taken"); + + //withdraw + const _devBefore: BigNumber = await want.balanceOf(devfund.address); + _treasuryBefore = await want.balanceOf(treasury.address); + console.log("\n👨‍🌾 Dev balance before picklejar withdrawal: ", _devBefore.toString()); + console.log("💸 Treasury balance before picklejar withdrawal: ", _treasuryBefore.toString()); + + await pickleJar.withdrawAll(); + + const _devAfter: BigNumber = await want.balanceOf(devfund.address); + _treasuryAfter = await want.balanceOf(treasury.address); + console.log("\n👨‍🌾 Dev balance after picklejar withdrawal: ", _devAfter.toString()); + console.log("💸 Treasury balance after picklejar withdrawal: ", _treasuryAfter.toString()); + + //0% goes to dev + const _devFund = _devAfter.sub(_devBefore); + expect(_devFund).to.be.eq(0, "dev've stolen money!!!!!"); + + //0% goes to treasury + const _treasuryFund = _treasuryAfter.sub(_treasuryBefore); + expect(_treasuryFund).to.be.eq(0, "treasury've stolen money!!!!"); + }); + + it("Should perform multiple deposits and withdrawals correctly", async () => { + const {want, alice, jar: pickleJar, strategist} = await loadFixture(initialSetupFixture); + const _wantHalved: BigNumber = (await want.balanceOf(alice.address)).div(2); + await want.connect(alice).transfer(strategist.address, _wantHalved); + await want.connect(alice).approve(pickleJar.address, _wantHalved); + await want.connect(strategist).approve(pickleJar.address, _wantHalved); + + console.log("\nAlice starting balance: %s\n", _wantHalved.toString()); + + await pickleJar.connect(alice).deposit(_wantHalved); + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * 2); //travel 2 days + + await pickleJar.connect(strategist).deposit(_wantHalved); + + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * 2); //travel 2 days + + // Alice withdraws half + await pickleJar.connect(alice).withdraw(_wantHalved.div(2)); + + await pickleJar.earn(); + + // Strategist withdraws all + await pickleJar.connect(strategist).withdrawAll(); + + let _aliceBalanceAfter: BigNumber = await want.balanceOf(alice.address); + let _strategistBalanceAfter: BigNumber = await want.balanceOf(strategist.address); + console.log("\nAlice balance after half withdrawal: %s\n", _aliceBalanceAfter.toString()); + console.log("\nStrategist balance after half withdrawal: %s\n", _strategistBalanceAfter.toString()); + + expect(_aliceBalanceAfter).to.be.approximately(_wantHalved.div(2), 1, "Alice withdrawal amount incorrect"); + + expect(_strategistBalanceAfter).to.be.approximately(_wantHalved, 1, "Strategist withdrawal amount incorrect"); + + // Alice withdraws remainder + + await pickleJar.connect(alice).withdrawAll(); + _aliceBalanceAfter = await want.balanceOf(alice.address); + console.log("\nAlice balance after full withdrawal: %s\n", _aliceBalanceAfter.toString()); + expect(_aliceBalanceAfter).to.be.approximately(_wantHalved, 1, "Alice withdrawal amount incorrect"); + }); + + it("should add, update and remove rewards correctly", async () => { + const {strategy, strategist} = await loadFixture(initialSetupFixture); + const encodeRouteStep = ( + isLegacy: boolean, + legacyPath: string[] = undefined, + tridentPath: string[][] = undefined + ) => { + let encodedPath: string; + if (isLegacy) { + encodedPath = ethers.utils.defaultAbiCoder.encode(["address[]"], [legacyPath]); + } else { + encodedPath = ethers.utils.defaultAbiCoder.encode(["tuple(address, bytes)[]"], [tridentPath]); + } + const encodedRouteStep = ethers.utils.defaultAbiCoder.encode(["bool", "bytes"], [isLegacy, encodedPath]); + return encodedRouteStep; + }; + const getToNativeRoute = async (rewardToken:string) => { + const want = await strategy.want(); + let rewardToNativeRoute: string[]; + if ((await strategy.bentoBox()) === ethers.constants.AddressZero) { + // Get a legacy route + const rewardLegacyPath = [rewardToken, want, await strategy.native()]; + rewardToNativeRoute = [encodeRouteStep(true, rewardLegacyPath)]; + } else { + // Get a trident route + const rewardTridentPath = [ + [ + want, // pool + ethers.utils.defaultAbiCoder.encode( + ["address", "address", "bool"], + [rewardToken, strategy.address, true] + ), // data + ], + ]; + rewardToNativeRoute = [encodeRouteStep(false, undefined, rewardTridentPath)]; + } + const rewardToNativeRouteEncoded = ethers.utils.defaultAbiCoder.encode(["bytes[]"], [rewardToNativeRoute]); + return rewardToNativeRouteEncoded; + }; + + // Add new reward + const newRewardToken = await strategy.want(); // Any token address that is not an existing reward token on the strategy + const newRewardToNativeRouteEncoded = await getToNativeRoute(newRewardToken); + + const rewardsBeforeAdd: string[] = await strategy.getActiveRewardsTokens(); + await strategy + .connect(strategist) + .addToNativeRoute(newRewardToNativeRouteEncoded); + const rewardsAfterAdd: string[] = await strategy.getActiveRewardsTokens(); + + expect(rewardsAfterAdd.length).to.be.eq(rewardsBeforeAdd.length + 1, "Adding reward failed"); + + // Update reward route + const rewardToken = await strategy.sushi(); + const rewardToNativeRouteEncoded = await getToNativeRoute(rewardToken); + await strategy + .connect(strategist) + .addToNativeRoute(rewardToNativeRouteEncoded); + const rewardsAfterUpdate: string[] = await strategy.getActiveRewardsTokens(); + + expect(rewardsAfterUpdate.length).to.be.eq( + rewardsAfterAdd.length, + "Updating reward path results in redundance in activeRewardsTokens" + ); + + // Remove a reward token + await strategy.connect(strategist).deactivateReward(rewardToken); + const rewardsAfterRemove: string[] = await strategy.getActiveRewardsTokens(); + + expect(rewardsAfterRemove.length).to.be.eq(rewardsAfterUpdate.length - 1, "Deactivating reward failed"); + }); + }); +}; + +// Helpers +const getWantFor = async (signer: SignerWithAddress, strategy: Contract) => { + const legacyRouterAbi = [ + "function addLiquidity(address tokenA,address tokenB,uint256 amountADesired,uint256 amountBDesired,uint256 amountAMin,uint256 amountBMin,address to,uint256 deadline) returns (uint256 amountA,uint256 amountB,uint256 liquidity)", + "function swapExactTokensForTokens(uint256 amountIn,uint256 amountOutMin,address[] path,address to,uint256 deadline) returns (uint256[] amounts)", + ]; + const tridentRouterAbi = [ + "function addLiquidity(tuple(address token, bool native, uint256 amount)[] tokenInput, address pool, uint256 minLiquidity, bytes data) payable returns(uint256)", + "function exactInputWithNativeToken(tuple(address tokenIn, uint256 amountIn, uint256 amountOutMinimum, tuple(address pool, bytes data)[])) payable returns(uint256 amountOut)", + ]; + const bentoAbi = [ + "function setMasterContractApproval(address user,address masterContract,bool approved,uint8 v,bytes32 r,bytes32 s)", + ]; + const legacyRouterAddr = await strategy.sushiRouter(); + const tridentRouterAddr = await strategy.tridentRouter(); + const bentoAddr = await strategy.bentoBox(); + const bentoBox = await ethers.getContractAt(bentoAbi, bentoAddr); + const legacyRouter = await ethers.getContractAt(legacyRouterAbi, legacyRouterAddr); + const tridentRouter = await ethers.getContractAt(tridentRouterAbi, tridentRouterAddr); + const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token0()); + const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.token1()); + const native = await getContractAt("src/lib/erc20.sol:ERC20", await strategy.native()); + const isBentoPool = await strategy.isBentoPool(); + + if (tridentRouter.address !== ethers.constants.AddressZero) { + await bentoBox + .connect(signer) + .setMasterContractApproval( + signer.address, + tridentRouter.address, + true, + 0, + ethers.constants.HashZero, + ethers.constants.HashZero + ); + } + + const nativeBal: BigNumber = await native.balanceOf(signer.address); + if (nativeBal.isZero()) throw "Signer have 0 native balance"; + + type LegacyPathStep = string; + type TridentPathStep = {pool: string; data: string}; + interface RouteStep { + isLegacy: boolean; + legacyPath: LegacyPathStep[]; + tridentPath: TridentPathStep[]; + } + const token0Routes: RouteStep[] = []; + const token0RoutesLength: number = await strategy + .getToTokenRouteLength(token0.address) + .then((x: BigNumber) => x.toNumber()); + for (let i = 0; i < token0RoutesLength; i++) { + const {isLegacy, legacyPath, tridentPath} = await strategy.getToTokenRoute(token0.address, i); + const route0: RouteStep = {isLegacy, legacyPath, tridentPath}; + token0Routes.push(route0); + } + + const token1Routes: RouteStep[] = []; + const token1RoutesLength: number = await strategy + .getToTokenRouteLength(token1.address) + .then((x: BigNumber) => x.toNumber()); + for (let i = 0; i < token1RoutesLength; i++) { + const {isLegacy, legacyPath, tridentPath} = await strategy.getToTokenRoute(token1.address, i); + const route1: RouteStep = {isLegacy, legacyPath, tridentPath}; + token1Routes.push(route1); + } + + const deadline = Math.ceil(Date.now() / 1000) + 300; + + // Swap to token0 + let swapAmount: BigNumber = nativeBal.div(2); + for (let i = 0; i < token0Routes.length; i++) { + const isLegacy = token0Routes[i].isLegacy; + if (isLegacy) { + const route = token0Routes[i].legacyPath; + const tokenInContract = await getContractAt("src/lib/erc20.sol:ERC20", route[0]); + await tokenInContract.connect(signer).approve(legacyRouter.address, ethers.constants.MaxUint256); + const amountsOut = await legacyRouter + .connect(signer) + .callStatic.swapExactTokensForTokens(swapAmount, 1, route, signer.address, deadline); + await legacyRouter.connect(signer).swapExactTokensForTokens(swapAmount, 1, route, signer.address, deadline); + swapAmount = amountsOut[amountsOut.length-1]; + } else { + const strategyRoute = token0Routes[i].tridentPath; + const [tokenIn] = ethers.utils.defaultAbiCoder.decode(["address", "address", "bool"], strategyRoute[0].data); + const tokenInContract = await getContractAt("src/lib/erc20.sol:ERC20", tokenIn); + await tokenInContract.connect(signer).approve(bentoBox.address, ethers.constants.MaxUint256); + + const userRoute = strategyRoute.map((r, idx) => { + const pool = r.pool; + const [tokenIn, receiver, unwrapBento] = ethers.utils.defaultAbiCoder.decode( + ["address", "address", "bool"], + r.data + ); + const data = ethers.utils.defaultAbiCoder.encode( + ["address", "address", "bool"], + [tokenIn, idx === strategyRoute.length - 1 ? signer.address : receiver, unwrapBento] + ); + return {pool, data}; + }); + const exactInputParams = [tokenInContract.address, swapAmount, 1, userRoute]; + const amountOut = await tridentRouter.connect(signer).callStatic.exactInputWithNativeToken(exactInputParams); + await tridentRouter.connect(signer).exactInputWithNativeToken(exactInputParams); + swapAmount = amountOut; + } + } + + // Swap to token1 + swapAmount = nativeBal.div(2); + for (let i = 0; i < token1Routes.length; i++) { + const isLegacy = token1Routes[i].isLegacy; + if (isLegacy) { + const route = token1Routes[i].legacyPath; + const tokenInContract = await getContractAt("src/lib/erc20.sol:ERC20", route[0]); + await tokenInContract.connect(signer).approve(legacyRouter.address, ethers.constants.MaxUint256); + const amountsOut: BigNumber[] = await legacyRouter + .connect(signer) + .callStatic.swapExactTokensForTokens(swapAmount, 1, route, signer.address, deadline); + await legacyRouter.connect(signer).swapExactTokensForTokens(swapAmount, 1, route, signer.address, deadline); + swapAmount = amountsOut[amountsOut.length-1]; + } else { + const strategyRoute = token1Routes[i].tridentPath; + const [tokenIn] = ethers.utils.defaultAbiCoder.decode(["address", "address", "bool"], strategyRoute[0].data); + const tokenInContract = await getContractAt("src/lib/erc20.sol:ERC20", tokenIn); + await tokenInContract.connect(signer).approve(bentoBox.address, ethers.constants.MaxUint256); + + const userRoute = strategyRoute.map((r, idx) => { + const pool = r.pool; + const [tokenIn, receiver, unwrapBento] = ethers.utils.defaultAbiCoder.decode( + ["address", "address", "bool"], + r.data + ); + const data = ethers.utils.defaultAbiCoder.encode( + ["address", "address", "bool"], + [tokenIn, idx === strategyRoute.length - 1 ? signer.address : receiver, unwrapBento] + ); + return {pool, data}; + }); + + const exactInputParams = [tokenInContract.address, swapAmount, 1, userRoute]; + + const amountOut = await tridentRouter.connect(signer).callStatic.exactInputWithNativeToken(exactInputParams); + await tridentRouter.connect(signer).exactInputWithNativeToken(exactInputParams); + swapAmount = amountOut; + } + } + + const token0Bal = await token0.balanceOf(signer.address); + const token1Bal = await token1.balanceOf(signer.address); + + if (isBentoPool) { + const tokenInput = [ + [token0.address, true, token0Bal], + [token1.address, true, token1Bal], + ]; + const data = ethers.utils.defaultAbiCoder.encode(["address"], [signer.address]); + await token0.connect(signer).approve(bentoBox.address, ethers.constants.MaxUint256); + await token1.connect(signer).approve(bentoBox.address, ethers.constants.MaxUint256); + await tridentRouter.connect(signer).addLiquidity(tokenInput, await strategy.want(), 1, data); + } else { + await token0.connect(signer).approve(legacyRouter.address, ethers.constants.MaxUint256); + await token1.connect(signer).approve(legacyRouter.address, ethers.constants.MaxUint256); + await legacyRouter + .connect(signer) + .addLiquidity(token0.address, token1.address, token0Bal, token1Bal, 0, 0, signer.address, deadline); + } +}; diff --git a/src/tests/strategies/uniswapv3/jar-migration.test.ts b/src/tests/strategies/uniswapv3/jar-migration.test.ts new file mode 100644 index 000000000..10cbc39cd --- /dev/null +++ b/src/tests/strategies/uniswapv3/jar-migration.test.ts @@ -0,0 +1,589 @@ +import "@nomicfoundation/hardhat-toolbox"; +import {ethers} from "hardhat"; +import {loadFixture, setBalance} from "@nomicfoundation/hardhat-network-helpers"; +import {BigNumber, Contract} from "ethers"; +import {expect, getContractAt} from "../../utils/testHelper"; +import {callExecuteToProxy, sendGnosisSafeTxn} from "../../utils/multisigHelper"; +import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; + +export const doJarMigrationTest = async ( + strategyContractName: string, + badStrategyAddress: string, + nativeAmountToSwap: number +) => { + describe("UniV3 jar + strategy migration", () => { + const initialSetupFixture = async () => { + const [governance, alice, bob, charles, fred] = await ethers.getSigners(); + + const badStrategy = await getContractAt(strategyContractName, badStrategyAddress); + const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await badStrategy.token0()); + const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await badStrategy.token1()); + + // Transfer governance on bad strategy + const timelockAddress = await badStrategy.governance(); + await sendGnosisSafeTxn(timelockAddress, badStrategy, "setTimelock", [governance.address]); + + // Controller setup + const controllerContractName = "/src/optimism/controller-v7.sol:ControllerV7"; + // const controllerContractName = "/src/controller-v7.sol:ControllerV7"; + const controllerAddress = await badStrategy.controller(); + const controller = await ethers.getContractAt(controllerContractName, controllerAddress); + const controllerGovernance = await controller.governance(); + const controllerTimelock = await controller.timelock(); + + // New strategy setup + const poolAddr = await badStrategy.pool(); + const poolAbi = [ + "function tickSpacing() view returns(int24)", + "function token0() view returns(address)", + "function token1() view returns(address)", + "function fee() view returns(uint24)", + ]; + const pool = await ethers.getContractAt(poolAbi, poolAddr); + const tickSpacing = await pool.tickSpacing(); + const utick = await badStrategy.tick_upper(); + const ltick = await badStrategy.tick_lower(); + const tickRangeMultiplier = (utick - ltick) / 2 / tickSpacing; + + const strategyContractFactory = await ethers.getContractFactory(strategyContractName); + const newStrategy = await strategyContractFactory.deploy( + tickRangeMultiplier, + governance.address, + governance.address, + controller.address, + governance.address + ); + const native = await getContractAt("src/lib/erc20.sol:ERC20", await newStrategy.native()); + + // New jar setup + const oldJar = await controller.jars(pool.address); + expect(await token0.balanceOf(oldJar)).to.be.eq( + BigNumber.from(0), + `Old jar still have ${await token0.symbol()} balance!` + ); + expect(await token1.balanceOf(oldJar)).to.be.eq( + BigNumber.from(0), + `Old jar still have ${await token1.symbol()} balance!` + ); + + const jarContract = "src/optimism/pickle-jar-univ3.sol:PickleJarUniV3"; + const jarContractFactory = await ethers.getContractFactory(jarContract); + const jarName = `pickling ${await token0.symbol()}/${await token1.symbol()} Jar`; + const jarSymbol = `p${await token0.symbol()}${await token1.symbol()}`; + const jar = await jarContractFactory.deploy( + jarName, + jarSymbol, + pool.address, + native.address, + governance.address, + governance.address, + controller.address + ); + await sendGnosisSafeTxn(controllerGovernance, controller, "setJar", [poolAddr, jar.address]); + + // Transfer Liquidity position from the bad strategy to alice + const tokenId = await badStrategy.tokenId(); + const nftManAddr = await badStrategy.nftManager(); + const nftManAbi = [ + "function transferFrom(address from, address to, uint256 tokenId)", + "function ownerOf(uint256) view returns(address)", + "function WETH9() view returns(address)", + "function decreaseLiquidity(tuple(uint256 tokenId, uint128 liquidity, uint256 amount0Min, uint256 amount1Min, uint256 deadline)) payable returns(uint256 amount0, uint256 amount1)", + "function collect(tuple(uint256 tokenId, address recipient, uint128 amount0Max, uint128 amount1Max)) payable returns(uint256 amount0, uint256 amount1)", + "function positions(uint256) 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)", + ]; + const nftManContract = await ethers.getContractAt(nftManAbi, nftManAddr); + + await callExecuteToProxy(governance, badStrategy, nftManContract, "transferFrom", [ + badStrategy.address, + alice.address, + tokenId, + ]); + + // Transfer badstrat dust to alice + const badStratBal0: BigNumber = await token0.balanceOf(badStrategy.address); + const badStratBal1: BigNumber = await token1.balanceOf(badStrategy.address); + if (!badStratBal0.isZero()) { + await callExecuteToProxy(governance, badStrategy, token0, "transfer", [alice.address, badStratBal0]); + } + if (!badStratBal1.isZero()) { + await callExecuteToProxy(governance, badStrategy, token1, "transfer", [alice.address, badStratBal1]); + } + + // Remove all liquidity from the NFT, then send NFT back to the badStrategy + const {liquidity} = await nftManContract.positions(tokenId); + const deadline = Math.floor(Date.now() / 1000) + 300; + const [amount0, amount1] = await nftManContract + .connect(alice) + .callStatic.decreaseLiquidity([tokenId, liquidity, 0, 0, deadline]); + await nftManContract.connect(alice).decreaseLiquidity([tokenId, liquidity, 0, 0, deadline]); + await nftManContract.connect(alice).collect([tokenId, alice.address, amount0.mul(2), amount1.mul(2)]); + await nftManContract.connect(alice).transferFrom(alice.address, badStrategy.address, tokenId); + + // Approve and set the new strategy on the controller + await sendGnosisSafeTxn(controllerTimelock, controller, "approveStrategy", [poolAddr, newStrategy.address]); + await sendGnosisSafeTxn(controllerGovernance, controller, "setStrategy", [poolAddr, newStrategy.address]); + + // Mint initial position on the new strategy + const aliceBalance0 = await token0.balanceOf(alice.address); + const aliceBalance1 = await token1.balanceOf(alice.address); + + await token0.connect(alice).transfer(newStrategy.address, aliceBalance0.div(10)); + await token1.connect(alice).transfer(newStrategy.address, aliceBalance1.div(10)); + + await newStrategy.connect(governance).rebalance(); + + // UniV3 router + const routerAbi = [ + "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) payable returns(uint256 amountOut)", + "function exactInput((bytes path,address recipient,uint256 amountIn,uint256 amountOutMinimum)) payable returns (uint256 amountOut)", + "function WETH9() view returns(address)", + "function factory() view returns(address)", + ]; + const routerAddr = await newStrategy.univ3Router(); + const router = await ethers.getContractAt(routerAbi, routerAddr); + + return { + badStrategy, + newStrategy, + jar, + governance, + token0, + token1, + native, + alice, + bob, + charles, + fred, + controller, + controllerGovernance, + router, + pool, + }; + }; + + it("Alice making initial deposit", async () => { + const {governance, newStrategy, token0, token1, alice, jar} = await loadFixture(initialSetupFixture); + const token0Symbol = await token0.symbol(); + const token1Symbol = await token1.symbol(); + const token0Decimals = await token0.decimals(); + const token1Decimals = await token1.decimals(); + const strategyAmount0Before = await token0.balanceOf(newStrategy.address); + const strategyAmount1Before = await token1.balanceOf(newStrategy.address); + + // Deposit all tokens in the jar + const aliceAmount0Before = await token0.balanceOf(alice.address); + const aliceAmount1Before = await token1.balanceOf(alice.address); + await token0.connect(alice).approve(jar.address, aliceAmount0Before); + await token1.connect(alice).approve(jar.address, aliceAmount1Before); + await jar.connect(alice).deposit(aliceAmount0Before, aliceAmount1Before); + + // Transfer refunded tokens to the strategy and call rebalance + const aliceRefundAmount0 = await token0.balanceOf(alice.address); + const aliceRefundAmount1 = await token1.balanceOf(alice.address); + await token0.connect(alice).transfer(newStrategy.address, aliceRefundAmount0); + await token1.connect(alice).transfer(newStrategy.address, aliceRefundAmount1); + await newStrategy.connect(governance).rebalance(); + const aliceAmount0After = await token0.balanceOf(alice.address); + const aliceAmount1After = await token1.balanceOf(alice.address); + const alicePTokenBalance: BigNumber = await jar.balanceOf(alice.address); + + console.log("==Alice=="); + console.log(token0Symbol + " before: " + bigToNumber(aliceAmount0Before, token0Decimals)); + console.log(token1Symbol + " before: " + bigToNumber(aliceAmount1Before, token1Decimals)); + console.log(token0Symbol + " after: " + bigToNumber(aliceAmount0After, token0Decimals)); + console.log(token1Symbol + " after: " + bigToNumber(aliceAmount1After, token1Decimals)); + console.log("pToken after: " + alicePTokenBalance.toString()); + expect(aliceAmount0After).to.be.eq(BigNumber.from(0), "Alice didn't deposit all token0 amounts!"); + expect(aliceAmount1After).to.be.eq(BigNumber.from(0), "Alice didn't deposit all token1 amounts!"); + expect(alicePTokenBalance.gt(0)).to.be.eq(true, "Deposit failed! Alice didn't get any pTokens."); + + console.log("\n==Strategy=="); + console.log(token0Symbol + " before: " + bigToNumber(strategyAmount0Before, token0Decimals)); + console.log(token1Symbol + " before: " + bigToNumber(strategyAmount1Before, token1Decimals)); + console.log(token0Symbol + " after: " + bigToNumber(await token0.balanceOf(newStrategy.address), token0Decimals)); + console.log(token1Symbol + " after: " + bigToNumber(await token1.balanceOf(newStrategy.address), token1Decimals)); + }); + + it("Should perform deposits and withdrawals correctly", async () => { + const { + governance, + controller, + controllerGovernance, + newStrategy, + token0, + token1, + native, + alice, + bob, + charles, + fred, + jar, + router, + pool, + } = await loadFixture(initialSetupFixture); + + const treasuryAddress = await controller.treasury(); + const treasuryToken0Before = await token0.balanceOf(treasuryAddress); + const treasuryToken1Before = await token1.balanceOf(treasuryAddress); + const treasuryNativeBefore = await native.balanceOf(treasuryAddress); + const token0Symbol = await token0.symbol(); + const token1Symbol = await token1.symbol(); + const token0Decimals = await token0.decimals(); + const token1Decimals = await token1.decimals(); + + // Set users' ETH balances so they can deposit in jars + const nativeAmountToSwapBN = ethers.utils.parseEther(nativeAmountToSwap.toString()); + const wethTopupAmount = nativeAmountToSwapBN.mul(2).add(ethers.utils.parseEther("1")); + await setBalance(bob.address, wethTopupAmount); + await setBalance(charles.address, wethTopupAmount); + await setBalance(fred.address, wethTopupAmount); + + // Fill users' token0/token1 balances + await buyToken(bob, nativeAmountToSwapBN, token0, router); + await buyToken(charles, nativeAmountToSwapBN, token0, router); + await buyToken(fred, nativeAmountToSwapBN, token0, router); + await buyToken(bob, nativeAmountToSwapBN, token1, router); + await buyToken(charles, nativeAmountToSwapBN, token1, router); + await buyToken(fred, nativeAmountToSwapBN, token1, router); + + console.log( + "Jar " + token0Symbol + " balance before test => ", + bigToNumber(await token0.balanceOf(jar.address), token0Decimals) + ); + console.log( + "Jar " + token1Symbol + " balance before test => ", + bigToNumber(await token1.balanceOf(jar.address), token1Decimals) + ); + console.log( + "Strategy " + token0Symbol + " balance before test => ", + bigToNumber(await token0.balanceOf(newStrategy.address), token0Decimals) + ); + console.log( + "Strategy " + token1Symbol + " balance before test => ", + bigToNumber(await token1.balanceOf(newStrategy.address), token1Decimals) + ); + + // Alice has to deposit first to preserve old jar users' shares + const aliceAmount0Before = await token0.balanceOf(alice.address); + const aliceAmount1Before = await token1.balanceOf(alice.address); + await token0.connect(alice).approve(jar.address, aliceAmount0Before); + await token1.connect(alice).approve(jar.address, aliceAmount1Before); + await jar.connect(alice).deposit(aliceAmount0Before, aliceAmount1Before); + const aliceRefundAmount0 = await token0.balanceOf(alice.address); + const aliceRefundAmount1 = await token1.balanceOf(alice.address); + await token0.connect(alice).transfer(newStrategy.address, aliceRefundAmount0); + await token1.connect(alice).transfer(newStrategy.address, aliceRefundAmount1); + await newStrategy.connect(governance).rebalance(); + + // New users depositing into the jar + // Fred deposit + console.log("\n=============== Fred Deposit =============="); + console.log( + "Fred " + token0Symbol + " balance before deposit => ", + bigToNumber(await token0.balanceOf(fred.address), token0Decimals) + ); + console.log( + "Fred " + token1Symbol + " balance before deposit => ", + bigToNumber(await token1.balanceOf(fred.address), token1Decimals) + ); + await depositIntoJar(fred, jar, token0, token1); + console.log( + "Fred " + token0Symbol + " balance after deposit => ", + bigToNumber(await token0.balanceOf(fred.address), token0Decimals) + ); + console.log( + "Fred " + token1Symbol + " balance after deposit => ", + bigToNumber(await token1.balanceOf(fred.address), token1Decimals) + ); + + // Bob & Charles deposits + console.log("\n=============== Bob Deposit =============="); + console.log( + "Bob " + token0Symbol + " balance before deposit => ", + bigToNumber(await token0.balanceOf(bob.address), token0Decimals) + ); + console.log( + "Bob " + token1Symbol + " balance before deposit => ", + bigToNumber(await token1.balanceOf(bob.address), token1Decimals) + ); + await depositIntoJar(bob, jar, token0, token1); + console.log( + "Bob " + token0Symbol + " balance after deposit => ", + bigToNumber(await token0.balanceOf(bob.address), token0Decimals) + ); + console.log( + "Bob " + token1Symbol + " balance after deposit => ", + bigToNumber(await token1.balanceOf(bob.address), token1Decimals) + ); + + console.log("\n=============== Charles Deposit =============="); + console.log( + "Charles " + token0Symbol + " balance before deposit => ", + bigToNumber(await token0.balanceOf(charles.address), token0Decimals) + ); + console.log( + "Charles " + token1Symbol + " balance before deposit => ", + bigToNumber(await token1.balanceOf(charles.address), token1Decimals) + ); + await depositIntoJar(charles, jar, token0, token1); + console.log( + "Charles " + token0Symbol + " balance after deposit => ", + bigToNumber(await token0.balanceOf(charles.address), token0Decimals) + ); + console.log( + "Charles " + token1Symbol + " balance after deposit => ", + bigToNumber(await token1.balanceOf(charles.address), token1Decimals) + ); + + // Users' shares should be close + const fredShare: BigNumber = await jar.balanceOf(fred.address); + const bobShare: BigNumber = await jar.balanceOf(bob.address); + const charlesShare: BigNumber = await jar.balanceOf(charles.address); + console.log("Fred share amount => ", bigToNumber(fredShare)); + console.log("Bob share amount => ", bigToNumber(bobShare)); + console.log("Charles share amount => ", bigToNumber(charlesShare)); + expect(fredShare).to.be.eqApprox(bobShare); + expect(fredShare).to.be.eqApprox(charlesShare); + + console.log("\n=============== Strategy Harvest =============="); + const liquidityBefore = await newStrategy.liquidityOfPool(); + console.log("Strategy liquidity before harvest => ", bigToNumber(liquidityBefore, 0)); + await simulateSwaps(router, pool, nativeAmountToSwapBN.mul(20)); + await newStrategy.connect(governance).harvest(); + const liquidityAfter: BigNumber = await newStrategy.liquidityOfPool(); + console.log("Strategy liquidity after harvest => ", bigToNumber(liquidityAfter, 0)); + expect(liquidityAfter.gt(liquidityBefore)).to.be.eq(true, "Harvest failed! Liquidity didn't increase."); + + console.log("\n=============== Fred partial withdraw =============="); + console.log( + "Fred " + token0Symbol + " balance before withdrawal => ", + bigToNumber(await token0.balanceOf(fred.address), token0Decimals) + ); + console.log( + "Fred " + token1Symbol + " balance before withdrawal => ", + bigToNumber(await token1.balanceOf(fred.address), token1Decimals) + ); + await jar.connect(fred).withdraw(fredShare.div(2)); + console.log( + "Fred " + token0Symbol + " balance after withdrawal => ", + bigToNumber(await token0.balanceOf(fred.address), token0Decimals) + ); + console.log( + "Fred " + token1Symbol + " balance after withdrawal => ", + bigToNumber(await token1.balanceOf(fred.address), token1Decimals) + ); + console.log("Fred shares remaining => ", bigToNumber(await jar.balanceOf(fred.address))); + + console.log("\n=============== Bob withdraw =============="); + console.log( + "Bob " + token0Symbol + " balance before withdrawal => ", + bigToNumber(await token0.balanceOf(bob.address), token0Decimals) + ); + console.log( + "Bob " + token1Symbol + " balance before withdrawal => ", + bigToNumber(await token1.balanceOf(bob.address), token1Decimals) + ); + await jar.connect(bob).withdrawAll(); + const bobBalance0After = await token0.balanceOf(bob.address); + const bobBalance1After = await token1.balanceOf(bob.address); + console.log( + "Bob " + token0Symbol + " balance after withdrawal => ", + bigToNumber(bobBalance0After, token0Decimals) + ); + console.log( + "Bob " + token1Symbol + " balance after withdrawal => ", + bigToNumber(bobBalance1After, token1Decimals) + ); + + console.log("Rebalancing..."); + await newStrategy.connect(governance).rebalance(); + + // Controller withdraws tokens from strategy to jar. Users should be able to withdraw when strategy is empty + console.log("\n=============== Controller withdraw ==============="); + console.log( + "Jar " + token0Symbol + " balance before withdrawal => ", + bigToNumber(await token0.balanceOf(jar.address), token0Decimals) + ); + console.log( + "Jar " + token1Symbol + " balance before withdrawal => ", + bigToNumber(await token1.balanceOf(jar.address), token1Decimals) + ); + await sendGnosisSafeTxn(controllerGovernance, controller, "withdrawAll", [pool.address]); + console.log( + "Jar " + token0Symbol + " balance after withdrawal => ", + bigToNumber(await token0.balanceOf(jar.address), token0Decimals) + ); + console.log( + "Jar " + token1Symbol + " balance after withdrawal => ", + bigToNumber(await token1.balanceOf(jar.address), token1Decimals) + ); + + console.log("\n=============== Fred Full withdraw =============="); + console.log( + "Fred " + token0Symbol + " balance before withdrawal => ", + bigToNumber(await token0.balanceOf(fred.address), token0Decimals) + ); + console.log( + "Fred " + token1Symbol + " balance before withdrawal => ", + bigToNumber(await token1.balanceOf(fred.address), token1Decimals) + ); + await jar.connect(fred).withdrawAll(); + const fredBalance0After = await token0.balanceOf(fred.address); + const fredBalance1After = await token1.balanceOf(fred.address); + console.log( + "Fred " + token0Symbol + " balance after withdrawal => ", + bigToNumber(fredBalance0After, token0Decimals) + ); + console.log( + "Fred " + token1Symbol + " balance after withdrawal => ", + bigToNumber(fredBalance1After, token1Decimals) + ); + + console.log("\n=============== charles withdraw =============="); + console.log( + "Charles " + token0Symbol + " balance before withdrawal => ", + bigToNumber(await token0.balanceOf(charles.address), token0Decimals) + ); + console.log( + "Charles " + token1Symbol + " balance before withdrawal => ", + bigToNumber(await token1.balanceOf(charles.address), token1Decimals) + ); + await jar.connect(charles).withdrawAll(); + const charlesBalance0After = await token0.balanceOf(charles.address); + const charlesBalance1After = await token1.balanceOf(charles.address); + console.log( + "Charles " + token0Symbol + " balance after withdrawal => ", + bigToNumber(charlesBalance0After, token0Decimals) + ); + console.log( + "Charles " + token1Symbol + " balance after withdrawal => ", + bigToNumber(charlesBalance1After, token1Decimals) + ); + + expect(bobBalance0After).to.be.eqApprox(charlesBalance0After); + expect(bobBalance0After).to.be.eqApprox(fredBalance0After); + expect(bobBalance1After).to.be.eqApprox(charlesBalance1After); + expect(bobBalance1After).to.be.eqApprox(fredBalance1After); + + console.log("\n------------------ Finished -----------------------"); + const treasuryToken0After = await token0.balanceOf(treasuryAddress); + const treasuryToken1After = await token1.balanceOf(treasuryAddress); + const treasuryNativeAfter = await native.balanceOf(treasuryAddress); + console.log( + "Treasury " + token0Symbol + " gained => ", + bigToNumber(treasuryToken0After.sub(treasuryToken0Before), token0Decimals) + ); + console.log( + "Treasury " + token1Symbol + " gained => ", + bigToNumber(treasuryToken1After.sub(treasuryToken1Before), token1Decimals) + ); + console.log("Treasury native gained => ", bigToNumber(treasuryNativeAfter.sub(treasuryNativeBefore))); + console.log( + "Strategy " + token0Symbol + " balance => ", + bigToNumber(await token0.balanceOf(newStrategy.address), token0Decimals) + ); + console.log( + "Strategy " + token1Symbol + " balance => ", + bigToNumber(await token1.balanceOf(newStrategy.address), token1Decimals) + ); + console.log("Strategy native balance => ", bigToNumber(await native.balanceOf(newStrategy.address))); + console.log( + "Jar " + token0Symbol + " balance => ", + bigToNumber(await token0.balanceOf(jar.address), token0Decimals) + ); + console.log( + "Jar " + token1Symbol + " balance => ", + bigToNumber(await token1.balanceOf(jar.address), token1Decimals) + ); + console.log("Jar native balance => ", bigToNumber(await native.balanceOf(jar.address))); + }); + }); + + // Helpers + const bigToNumber = (amount: BigNumber, decimals = 18) => parseFloat(ethers.utils.formatUnits(amount, decimals)); + + const buyToken = async (buyer: SignerWithAddress, ethAmountToSwap: BigNumber, token: Contract, router: Contract) => { + // Convert ETH to WETH + const wethAddr = await router.WETH9(); + const wethDepositAbi = ["function deposit() payable", "function approve(address,uint256) returns(bool)"]; + const weth = await ethers.getContractAt(wethDepositAbi, wethAddr); + await weth.connect(buyer).deposit({value: ethAmountToSwap}); + + if (wethAddr === token.address) return; + + // Find the best pool for the swap + const factoryAbi = ["function getPool(address token0, address token1, uint24 fee) view returns(address)"]; + const factoryAddr = await router.factory(); + const factory = await ethers.getContractAt(factoryAbi, factoryAddr); + const fees = [100, 500, 3000]; + let bestPoolBal: BigNumber = BigNumber.from("0"); + let bestPoolFee: number; + for (let i = 0; i < fees.length; i++) { + const fee = fees[i]; + const poolAddr = await factory.getPool(wethAddr, token.address, fee); + if (poolAddr === ethers.constants.AddressZero) continue; + const poolBalance: BigNumber = await token.balanceOf(poolAddr); + if (poolBalance.gt(bestPoolBal)) { + bestPoolBal = poolBalance; + bestPoolFee = fee; + } + } + if (!bestPoolFee) throw `No pool found for WETH-${await token.symbol()}`; + + // Swap + const pathEncoded = ethers.utils.solidityPack( + ["address", "uint24", "address"], + [wethAddr, bestPoolFee, token.address] + ); + const exactInputParams = [pathEncoded, buyer.address, ethAmountToSwap, 0]; + await weth.connect(buyer).approve(router.address, ethers.constants.MaxUint256); + await router.connect(buyer).exactInput(exactInputParams); + }; + + const simulateSwaps = async (router: Contract, pool: Contract, nativeAmountToSwap: BigNumber) => { + const [alice] = await ethers.getSigners(); + const token0 = await getContractAt("src/lib/erc20.sol:ERC20", await pool.token0()); + const token1 = await getContractAt("src/lib/erc20.sol:ERC20", await pool.token1()); + const initialBalance0 = await token0.balanceOf(alice.address); + const initialBalance1 = await token1.balanceOf(alice.address); + await setBalance(alice.address, nativeAmountToSwap.add(ethers.utils.parseEther("1"))); + + // Convert native to token0 + await buyToken(alice, nativeAmountToSwap, token0, router); + + // Swap token0 to token1 + const amount0ToSwap = (await token0.balanceOf(alice.address)).sub(initialBalance0); + const pathEncoded0 = ethers.utils.solidityPack( + ["address", "uint24", "address"], + [token0.address, await pool.fee(), token1.address] + ); + const exactInputParams0 = [pathEncoded0, alice.address, amount0ToSwap, 0]; + await token0.connect(alice).approve(router.address, ethers.constants.MaxUint256); + await router.connect(alice).exactInput(exactInputParams0); + + // Swap token1 to token0 + const amount1ToSwap = (await token1.balanceOf(alice.address)).sub(initialBalance1); + const pathEncoded1 = ethers.utils.solidityPack( + ["address", "uint24", "address"], + [token1.address, await pool.fee(), token0.address] + ); + const exactInputParams1 = [pathEncoded1, alice.address, amount1ToSwap, 0]; + await token1.connect(alice).approve(router.address, ethers.constants.MaxUint256); + await router.connect(alice).exactInput(exactInputParams1); + + // Burn the residuals + const amount0ToBurn = (await token0.balanceOf(alice.address)).sub(initialBalance0); + const amount1ToBurn = (await token1.balanceOf(alice.address)).sub(initialBalance1); + await token0.connect(alice).transfer("0x0000000000000000000000000000000000000001", amount0ToBurn); + await token1.connect(alice).transfer("0x0000000000000000000000000000000000000001", amount1ToBurn); + }; + + const depositIntoJar = async (user: SignerWithAddress, jar: Contract, token0: Contract, token1: Contract) => { + const token0Bal = await token0.balanceOf(user.address); + const token1Bal = await token1.balanceOf(user.address); + await token0.connect(user).approve(jar.address, token0Bal); + await token1.connect(user).approve(jar.address, token1Bal); + await jar.connect(user).deposit(token0Bal, token1Bal); + }; +}; diff --git a/src/tests/strategies/uniswapv3/migrations/ape-eth.test.ts b/src/tests/strategies/uniswapv3/migrations/ape-eth.test.ts new file mode 100644 index 000000000..2ec64f4b4 --- /dev/null +++ b/src/tests/strategies/uniswapv3/migrations/ape-eth.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../jar-migration.test"; + +describe("StrategyApeEthUniV3 Migration Test", () => { + const newStrategyContractName = + "src/strategies/uniswapv3/strategy-univ3-ape-eth-lp.sol:StrategyApeEthUniV3"; + const oldStrategy = "0x5e20293615A4Caa3E2a9B5D24B40DBB176Ec01a8"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/uniswapv3/migrations/btc-eth.test.ts b/src/tests/strategies/uniswapv3/migrations/btc-eth.test.ts new file mode 100644 index 000000000..931ef93c1 --- /dev/null +++ b/src/tests/strategies/uniswapv3/migrations/btc-eth.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../jar-migration.test"; + +describe("StrategyWbtcEthUniV3 Migration Test", () => { + const newStrategyContractName = + "src/strategies/uniswapv3/strategy-univ3-wbtc-eth-lp.sol:StrategyWbtcEthUniV3"; + const oldStrategy = "0xae2e6daA0FD5c098C8cE87Df573E32C9d6493384"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/uniswapv3/migrations/eth-cow.test.ts b/src/tests/strategies/uniswapv3/migrations/eth-cow.test.ts new file mode 100644 index 000000000..e3fe59463 --- /dev/null +++ b/src/tests/strategies/uniswapv3/migrations/eth-cow.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../jar-migration.test"; + +describe("StrategyEthCowUniV3 Migration Test", () => { + const newStrategyContractName = + "src/strategies/uniswapv3/strategy-univ3-eth-cow-lp.sol:StrategyEthCowUniV3"; + const oldStrategy = "0x3B63E25e9fD76F152b4a2b6DfBfC402c5ba19A01"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/uniswapv3/migrations/usdc-eth.test.ts b/src/tests/strategies/uniswapv3/migrations/usdc-eth.test.ts new file mode 100644 index 000000000..32cd29bec --- /dev/null +++ b/src/tests/strategies/uniswapv3/migrations/usdc-eth.test.ts @@ -0,0 +1,10 @@ +import {doJarMigrationTest} from "../jar-migration.test"; + +describe("StrategyUsdcEth05UniV3 Migration Test", () => { + const newStrategyContractName = + "src/strategies/uniswapv3/strategy-univ3-usdc-eth-05-lp.sol:StrategyUsdcEth05UniV3"; + const oldStrategy = "0xd33d3D71C6F710fb7A94469ED958123Ab86858b1"; + const nativeAmountToDeposit = 0.1; + + doJarMigrationTest(newStrategyContractName, oldStrategy, nativeAmountToDeposit); +}); \ No newline at end of file diff --git a/src/tests/strategies/uniswapv3/strategy-eth-cow-rebalance.test.js b/src/tests/strategies/uniswapv3/strats/strategy-eth-cow-rebalance.test.js similarity index 100% rename from src/tests/strategies/uniswapv3/strategy-eth-cow-rebalance.test.js rename to src/tests/strategies/uniswapv3/strats/strategy-eth-cow-rebalance.test.js diff --git a/src/tests/strategies/uniswapv3/strategy-eth-looks-rebalance.test.js b/src/tests/strategies/uniswapv3/strats/strategy-eth-looks-rebalance.test.js similarity index 100% rename from src/tests/strategies/uniswapv3/strategy-eth-looks-rebalance.test.js rename to src/tests/strategies/uniswapv3/strats/strategy-eth-looks-rebalance.test.js diff --git a/src/tests/strategies/uniswapv3/strategy-eth-pickle-rebalance.test.js b/src/tests/strategies/uniswapv3/strats/strategy-eth-pickle-rebalance.test.js similarity index 100% rename from src/tests/strategies/uniswapv3/strategy-eth-pickle-rebalance.test.js rename to src/tests/strategies/uniswapv3/strats/strategy-eth-pickle-rebalance.test.js diff --git a/src/tests/strategies/uniswapv3/strategy-usdc-eth-3-rebalance.test.js b/src/tests/strategies/uniswapv3/strats/strategy-usdc-eth-3-rebalance.test.js similarity index 100% rename from src/tests/strategies/uniswapv3/strategy-usdc-eth-3-rebalance.test.js rename to src/tests/strategies/uniswapv3/strats/strategy-usdc-eth-3-rebalance.test.js diff --git a/src/tests/strategies/uniswapv3/strategy-usdc-eth-5-rebalance.test.js b/src/tests/strategies/uniswapv3/strats/strategy-usdc-eth-5-rebalance.test.js similarity index 100% rename from src/tests/strategies/uniswapv3/strategy-usdc-eth-5-rebalance.test.js rename to src/tests/strategies/uniswapv3/strats/strategy-usdc-eth-5-rebalance.test.js diff --git a/src/tests/strategies/uniswapv3/strategy-usdc-usdt-rebalance.test.js b/src/tests/strategies/uniswapv3/strats/strategy-usdc-usdt-rebalance.test.js similarity index 100% rename from src/tests/strategies/uniswapv3/strategy-usdc-usdt-rebalance.test.js rename to src/tests/strategies/uniswapv3/strats/strategy-usdc-usdt-rebalance.test.js diff --git a/src/tests/strategies/uniswapv3/strategy-wbtc-eth-rebalance.test.js b/src/tests/strategies/uniswapv3/strats/strategy-wbtc-eth-rebalance.test.js similarity index 100% rename from src/tests/strategies/uniswapv3/strategy-wbtc-eth-rebalance.test.js rename to src/tests/strategies/uniswapv3/strats/strategy-wbtc-eth-rebalance.test.js diff --git a/src/tests/utils/multisigHelper.ts b/src/tests/utils/multisigHelper.ts new file mode 100644 index 000000000..60c9cc688 --- /dev/null +++ b/src/tests/utils/multisigHelper.ts @@ -0,0 +1,176 @@ +import { SafeTransactionDataPartial, SafeTransaction } from "@safe-global/safe-core-sdk-types"; +import Safe from "@safe-global/safe-core-sdk"; +import EthersAdapter from "@safe-global/safe-ethers-lib"; +import { config, ethers, run } from "hardhat"; +import { BigNumber, Contract } from "ethers"; +import { deployContract, unlockAccount } from "./testHelper"; +import { writeFileSync } from "fs"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; + +/** + * @notice takes a gnosis safe address and returns a list of Safe instances each with a unique owner, + * the length of the array is the minimum threshold needed to execute a gnosis safe transaction + */ +const getSafesWithOwners = async (safeAddress: string): Promise => { + // prettier-ignore + const safeAbi = ["function getOwners() view returns(address[])", "function getThreshold() view returns(uint256)"]; + const safeContract = new ethers.Contract(safeAddress, safeAbi, await ethers.getSigners().then((x) => x[0])); + const ownersAddresses: string[] = await safeContract.getOwners(); + const threshold = await safeContract.getThreshold().then((x: BigNumber) => x.toNumber()); + const neededOwnersAddresses = ownersAddresses.slice(0, threshold); + + const ethAdapters: EthersAdapter[] = await Promise.all( + neededOwnersAddresses.map(async (ownerAddr) => new EthersAdapter({ ethers, signerOrProvider: await unlockAccount(ownerAddr) })) + ); + + const safeWithOwners: Safe[] = await Promise.all( + ethAdapters.map(async (ethAdapter) => await Safe.create({ safeAddress, ethAdapter })) + ); + + return safeWithOwners; +}; +/** + * @param targetContract the target contract + * @param funcName the target function name on the contract. (e.g, "transfer") + * @param funcArgs the arguments of the target function (e.g, ["0x000...",BigNumber.from("10000000000")] + */ +const getSafeTxnData = (targetContract: Contract, funcName: string, funcArgs: any[]) => { + const data = targetContract.interface.encodeFunctionData(funcName, funcArgs); + const safeTransactionData: SafeTransactionDataPartial = { + to: targetContract.address, + data, + value: "0", + safeTxGas: 1800000, + }; + return safeTransactionData; +}; + +const getSafeToProxyTxnData = (targetContract: Contract, funcName: string, funcArgs: any[]) => { + const data = targetContract.interface.encodeFunctionData(funcName, funcArgs); + const safeTransactionData: SafeTransactionDataPartial = { + to: targetContract.address, + data, + value: "0", + safeTxGas: 1800000, + }; + return safeTransactionData; +}; + +const executeSafeTxn = async (safeTransactionData: SafeTransactionDataPartial, safesWithOwners: Safe[]) => { + const safeTxn: SafeTransaction = await safesWithOwners[0].createTransaction({ safeTransactionData }); + const txHash = await safesWithOwners[0].getTransactionHash(safeTxn); + + // Ensure owner has enough eth to execute txns + const minBalance = ethers.utils.parseEther("0.1"); + const ownerAddress = await safesWithOwners[0].getEthAdapter().getSignerAddress(); + const ownerBalance = await safesWithOwners[0].getEthAdapter().getBalance(ownerAddress); + if (ownerBalance.lt(minBalance)) { + await setBalance(ownerAddress, minBalance.mul(2)); + } + + await Promise.all( + safesWithOwners.map(async (safe) => await (await safe.approveTransactionHash(txHash)).transactionResponse?.wait()) + ); + + return safesWithOwners[0].executeTransaction(safeTxn).then((x) => x.transactionResponse.wait()); +}; + +const writeProxyToFile = (proxyContract: Contract, funcName: string) => { + const convert = (funcSignature: string, argsNames: string[], withTypes: boolean) => { + let result = funcSignature; + let index = 0; + for (let i = 0; i < argsNames.length; i++) { + const argName = argsNames[i]; + + let startIndex = result.indexOf("(", index); + if (startIndex === -1) { + startIndex = result.indexOf(",", index); + } + + let endIndex = result.indexOf(",", startIndex + 1); + if (endIndex === -1) { + endIndex = result.indexOf(")", startIndex + 1); + } + + const type = result.substring(startIndex + 1, endIndex); + const typeStr = withTypes ? type + " " : ""; + result = result.substring(0, startIndex + 1) + typeStr + argName + result.substring(endIndex); + index = startIndex + 1; + } + return result; + }; + + const funcSignature = Object.keys(proxyContract.interface.functions).find((f) => f.startsWith(funcName)); + const nArgs = (funcSignature.match(/,/g) || []).length; + const alpha = Array.from(Array(nArgs + 1)).map((_, i) => i + 65); + const argsNames = alpha.map((x) => String.fromCharCode(x).toLowerCase()); + + const funcSigWithNames = convert(funcSignature, argsNames, true); + const funcSigWithoutTypes = convert(funcSignature, argsNames, false); + + const code = `// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +pragma experimental ABIEncoderV2; + + +interface ITarget { + function ${funcSignature} external; +} + +contract Proxy { + function ${funcSigWithNames} external { + ITarget(${ethers.utils.getAddress(proxyContract.address)}).${funcSigWithoutTypes}; + } +}`; + writeFileSync("src/tmp/proxy.sol", code); +}; + +const compileDir = async (dirPath: string) => { + // Limit Hardhat to only compile the supplied dir + const oldSourcesPath = config.paths.sources; + config.paths.sources = dirPath; + await run("compile"); + config.paths.sources = oldSourcesPath; +}; + +export const sendGnosisSafeTxn = async ( + safeAddress: string, + targetContract: Contract, + funcName: string, + funcArgs: any[] +) => { + const safesWithOwners = await getSafesWithOwners(safeAddress); + const txnData = getSafeTxnData(targetContract, funcName, funcArgs); + const txnReceipt = await executeSafeTxn(txnData, safesWithOwners); + return txnReceipt; +}; + +export const sendGnosisSafeProxyTxn = async ( + safeAddress: string, + targetContract: Contract, + targetFuncName: string, + targetFuncArgs: any[], + proxyContract: Contract, + proxyFuncName: string +) => { + writeProxyToFile(proxyContract, proxyFuncName); + await compileDir("src/tmp"); + const proxyContract1 = await deployContract("src/tmp/proxy.sol:Proxy"); + targetFuncArgs[0] = proxyContract1.address; + return sendGnosisSafeTxn(safeAddress, targetContract, targetFuncName, targetFuncArgs); +}; + +export const callExecuteToProxy = async ( + permissionedSigner: SignerWithAddress, + executeContract: Contract, + targetContract: Contract, + targetFuncName: string, + targetFuncArgs: any[], +) => { + writeProxyToFile(targetContract, targetFuncName); + await compileDir("src/tmp"); + const proxyContract1 = await deployContract("src/tmp/proxy.sol:Proxy"); + const txnData = proxyContract1.interface.encodeFunctionData(targetFuncName, targetFuncArgs); + return await executeContract.connect(permissionedSigner).execute(proxyContract1.address, txnData).then(x=>x.wait()); +} diff --git a/src/tests/utils/setupHelper.ts b/src/tests/utils/setupHelper.ts index 7ca1ec6b9..6f7aa804d 100644 --- a/src/tests/utils/setupHelper.ts +++ b/src/tests/utils/setupHelper.ts @@ -20,7 +20,6 @@ const now = () => { /** * @notice setup the controller, strategy and picklejar * @param strategyName strategy name to be deployed - * @param want want token to be set * @param governance governance signer addr * @param strategist strategy signer addr * @param timelock timelock signer addr @@ -30,18 +29,14 @@ const now = () => { */ export const setup = async ( strategyName: string, - want: Contract, governance: SignerWithAddress, strategist: SignerWithAddress, timelock: SignerWithAddress, devfund: SignerWithAddress, treasury: SignerWithAddress, - isPolygon = false ) => { const controller = await deployContract( - isPolygon - ? "src/polygon/controller-v4.sol:ControllerV4" - : "src/controller-v4.sol:ControllerV4", + "src/controller-v4.sol:ControllerV4", governance.address, strategist.address, timelock.address, @@ -59,18 +54,19 @@ export const setup = async ( ); console.log("✅ Strategy is deployed at ", strategy.address); + const wantAddr = await strategy.want(); const pickleJar = await deployContract( "src/pickle-jar.sol:PickleJar", - want.address, + wantAddr, governance.address, timelock.address, controller.address ); console.log("✅ PickleJar is deployed at ", pickleJar.address); - await controller.setJar(want.address, pickleJar.address); - await controller.approveStrategy(want.address, strategy.address); - await controller.setStrategy(want.address, strategy.address); + await controller.setJar(wantAddr, pickleJar.address); + await controller.approveStrategy(wantAddr, strategy.address); + await controller.setStrategy(wantAddr, strategy.address); return [controller, strategy, pickleJar]; }; @@ -254,7 +250,7 @@ export const getLpToken = async ( /** * @dev get want token from the whale using impersonating account feature - * @param want want token instance + * @param want_addr want token instance * @param amount token amount * @param to receive address * @param whaleAddr whale address to send tokens @@ -266,11 +262,8 @@ export const getWantFromWhale = async ( whaleAddr: string ) => { const whale = await unlockAccount(whaleAddr); - console.log("here"); const want = await getContractAt("src/lib/erc20.sol:ERC20", want_addr); - console.log("can't get?", whale._address); await want.connect(whale).transfer(to.address, amount); - console.log("here2"); const _balance = await want.balanceOf(to.address); expect(_balance).to.be.gte(amount, "get want from the whale failed"); }; diff --git a/src/tests/utils/testHelper.ts b/src/tests/utils/testHelper.ts index a641e1f5c..5447a84cc 100644 --- a/src/tests/utils/testHelper.ts +++ b/src/tests/utils/testHelper.ts @@ -1,7 +1,9 @@ import "@nomicfoundation/hardhat-toolbox"; import { BigNumber, BigNumberish, providers } from "ethers"; import { ethers, network } from "hardhat"; -import * as chai from "chai"; +import * as chai from 'chai'; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; + /** * @notice travel the time to test the block.timestamp @@ -51,9 +53,8 @@ export const getContractAt = async (name: string, address: string) => { */ export const unlockAccount = async ( address: string -): Promise => { - await network.provider.send("hardhat_impersonateAccount", [address]); - return ethers.provider.getSigner(address); +): Promise => { + return ethers.getImpersonatedSigner(address); }; /** diff --git a/src/tmp/.gitignore b/src/tmp/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/src/tmp/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/yarn.lock b/yarn.lock index 6f7f52300..8f3c00bc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -104,31 +104,7 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@ethereumjs/block@^3.5.0", "@ethereumjs/block@^3.6.2", "@ethereumjs/block@^3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-3.6.3.tgz#d96cbd7af38b92ebb3424223dbf773f5ccd27f84" - integrity sha512-CegDeryc2DVKnDkg5COQrE0bJfw/p0v3GBk2W5/Dj5dOVfEmb50Ux0GLnSPypooLnfqjwFaorGuT9FokWB3GRg== - dependencies: - "@ethereumjs/common" "^2.6.5" - "@ethereumjs/tx" "^3.5.2" - ethereumjs-util "^7.1.5" - merkle-patricia-tree "^4.2.4" - -"@ethereumjs/blockchain@^5.5.2", "@ethereumjs/blockchain@^5.5.3": - version "5.5.3" - resolved "https://registry.yarnpkg.com/@ethereumjs/blockchain/-/blockchain-5.5.3.tgz#aa49a6a04789da6b66b5bcbb0d0b98efc369f640" - integrity sha512-bi0wuNJ1gw4ByNCV56H0Z4Q7D+SxUbwyG12Wxzbvqc89PXLRNR20LBcSUZRKpN0+YCPo6m0XZL/JLio3B52LTw== - dependencies: - "@ethereumjs/block" "^3.6.2" - "@ethereumjs/common" "^2.6.4" - "@ethereumjs/ethash" "^1.1.0" - debug "^4.3.3" - ethereumjs-util "^7.1.5" - level-mem "^5.0.1" - lru-cache "^5.1.1" - semaphore-async-await "^1.5.1" - -"@ethereumjs/common@^2.5.0", "@ethereumjs/common@^2.6.4", "@ethereumjs/common@^2.6.5": +"@ethereumjs/common@^2.5.0", "@ethereumjs/common@^2.6.4": version "2.6.5" resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.5.tgz#0a75a22a046272579d91919cb12d84f2756e8d30" integrity sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA== @@ -136,18 +112,7 @@ crc-32 "^1.2.0" ethereumjs-util "^7.1.5" -"@ethereumjs/ethash@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/ethash/-/ethash-1.1.0.tgz#7c5918ffcaa9cb9c1dc7d12f77ef038c11fb83fb" - integrity sha512-/U7UOKW6BzpA+Vt+kISAoeDie1vAvY4Zy2KF5JJb+So7+1yKmJeJEHOGSnQIj330e9Zyl3L5Nae6VZyh2TJnAA== - dependencies: - "@ethereumjs/block" "^3.5.0" - "@types/levelup" "^4.3.0" - buffer-xor "^2.0.1" - ethereumjs-util "^7.1.1" - miller-rabin "^4.0.0" - -"@ethereumjs/tx@^3.3.2", "@ethereumjs/tx@^3.5.1", "@ethereumjs/tx@^3.5.2": +"@ethereumjs/tx@^3.3.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.5.2.tgz#197b9b6299582ad84f9527ca961466fce2296c1c" integrity sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw== @@ -155,25 +120,7 @@ "@ethereumjs/common" "^2.6.4" ethereumjs-util "^7.1.5" -"@ethereumjs/vm@^5.9.0": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@ethereumjs/vm/-/vm-5.9.3.tgz#6d69202e4c132a4a1e1628ac246e92062e230823" - integrity sha512-Ha04TeF8goEglr8eL7hkkYyjhzdZS0PsoRURzYlTF6I0VVId5KjKb0N7MrA8GMgheN+UeTncfTgYx52D/WhEmg== - dependencies: - "@ethereumjs/block" "^3.6.3" - "@ethereumjs/blockchain" "^5.5.3" - "@ethereumjs/common" "^2.6.5" - "@ethereumjs/tx" "^3.5.2" - async-eventemitter "^0.2.4" - core-js-pure "^3.0.1" - debug "^4.3.3" - ethereumjs-util "^7.1.5" - functional-red-black-tree "^1.0.1" - mcl-wasm "^0.7.1" - merkle-patricia-tree "^4.2.4" - rustbn.js "~0.2.0" - -"@ethersproject/abi@5.6.4", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.6.4": +"@ethersproject/abi@5.6.4", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.6.3": version "5.6.4" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.6.4.tgz#f6e01b6ed391a505932698ecc0d9e7a99ee60362" integrity sha512-TTeZUlCeIHG6527/2goZA6gW5F8Emoc7MrZDC7hhP84aRGvW3TEdTnZR08Ls88YXM1m2SuK42Osw/jSi3uO8gg== @@ -188,6 +135,21 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.1" +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/abstract-provider@5.6.1", "@ethersproject/abstract-provider@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.6.1.tgz#02ddce150785caf0c77fe036a0ebfcee61878c59" @@ -201,6 +163,19 @@ "@ethersproject/transactions" "^5.6.2" "@ethersproject/web" "^5.6.1" +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + "@ethersproject/abstract-signer@5.6.2", "@ethersproject/abstract-signer@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.6.2.tgz#491f07fc2cbd5da258f46ec539664713950b0b33" @@ -212,6 +187,17 @@ "@ethersproject/logger" "^5.6.0" "@ethersproject/properties" "^5.6.0" +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/address@5.6.1", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.1.tgz#ab57818d9aefee919c5721d28cd31fd95eff413d" @@ -223,6 +209,17 @@ "@ethersproject/logger" "^5.6.0" "@ethersproject/rlp" "^5.6.1" +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/base64@5.6.1", "@ethersproject/base64@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.6.1.tgz#2c40d8a0310c9d1606c2c37ae3092634b41d87cb" @@ -230,6 +227,13 @@ dependencies: "@ethersproject/bytes" "^5.6.1" +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/basex@5.6.1", "@ethersproject/basex@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.6.1.tgz#badbb2f1d4a6f52ce41c9064f01eab19cc4c5305" @@ -238,6 +242,14 @@ "@ethersproject/bytes" "^5.6.1" "@ethersproject/properties" "^5.6.0" +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/bignumber@5.6.2", "@ethersproject/bignumber@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.6.2.tgz#72a0717d6163fab44c47bcc82e0c550ac0315d66" @@ -247,6 +259,15 @@ "@ethersproject/logger" "^5.6.0" bn.js "^5.2.1" +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + "@ethersproject/bytes@5.6.1", "@ethersproject/bytes@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.1.tgz#24f916e411f82a8a60412344bf4a813b917eefe7" @@ -254,6 +275,13 @@ dependencies: "@ethersproject/logger" "^5.6.0" +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/constants@5.6.1", "@ethersproject/constants@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.6.1.tgz#e2e974cac160dd101cf79fdf879d7d18e8cb1370" @@ -261,6 +289,13 @@ dependencies: "@ethersproject/bignumber" "^5.6.2" +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/contracts@5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.6.2.tgz#20b52e69ebc1b74274ff8e3d4e508de971c287bc" @@ -277,15 +312,31 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/transactions" "^5.6.2" -"@ethersproject/hardware-wallets@^5.6.1": - version "5.6.1" - resolved "https://registry.yarnpkg.com/@ethersproject/hardware-wallets/-/hardware-wallets-5.6.1.tgz#d99ee1c6169bd9f3423c386e3b11e0ab09f34964" - integrity sha512-9BnZiEhCsDhbjrP46Zqe979iZBXW07zy29e5WTjiuew1vpl2Defq3s5Tu8Vc2fThz7Ibcwn/CrAKQlBevkxF1w== +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/hardware-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hardware-wallets/-/hardware-wallets-5.7.0.tgz#1c902fc255e2f108af44d4c1dc46ec2c34cb669c" + integrity sha512-DjMMXIisRc8xFvEoLoYz1w7JDOYmaz/a0X9sp7Zu668RR8U1zCAyj5ow25HLRW+TCzEC5XiFetTXqS5kXonFCQ== dependencies: "@ledgerhq/hw-app-eth" "5.27.2" "@ledgerhq/hw-transport" "5.26.0" "@ledgerhq/hw-transport-u2f" "5.26.0" - ethers "^5.6.8" + ethers "^5.7.0" optionalDependencies: "@ledgerhq/hw-transport-node-hid" "5.26.0" @@ -303,6 +354,21 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.1" +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/hdnode@5.6.2", "@ethersproject/hdnode@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.6.2.tgz#26f3c83a3e8f1b7985c15d1db50dc2903418b2d2" @@ -321,6 +387,24 @@ "@ethersproject/transactions" "^5.6.2" "@ethersproject/wordlists" "^5.6.1" +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + "@ethersproject/json-wallets@5.6.1", "@ethersproject/json-wallets@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.6.1.tgz#3f06ba555c9c0d7da46756a12ac53483fe18dd91" @@ -340,6 +424,25 @@ aes-js "3.0.0" scrypt-js "3.0.1" +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + "@ethersproject/keccak256@5.6.1", "@ethersproject/keccak256@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.6.1.tgz#b867167c9b50ba1b1a92bccdd4f2d6bd168a91cc" @@ -348,11 +451,24 @@ "@ethersproject/bytes" "^5.6.1" js-sha3 "0.8.0" +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + "@ethersproject/logger@5.6.0", "@ethersproject/logger@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a" integrity sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg== +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + "@ethersproject/networks@5.6.4", "@ethersproject/networks@^5.6.3": version "5.6.4" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.6.4.tgz#51296d8fec59e9627554f5a8a9c7791248c8dc07" @@ -360,6 +476,13 @@ dependencies: "@ethersproject/logger" "^5.6.0" +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2@5.6.1", "@ethersproject/pbkdf2@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.6.1.tgz#f462fe320b22c0d6b1d72a9920a3963b09eb82d1" @@ -368,6 +491,14 @@ "@ethersproject/bytes" "^5.6.1" "@ethersproject/sha2" "^5.6.1" +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/properties@5.6.0", "@ethersproject/properties@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.6.0.tgz#38904651713bc6bdd5bdd1b0a4287ecda920fa04" @@ -375,7 +506,14 @@ dependencies: "@ethersproject/logger" "^5.6.0" -"@ethersproject/providers@5.6.8", "@ethersproject/providers@^5.6.8": +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@5.6.8": version "5.6.8" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.6.8.tgz#22e6c57be215ba5545d3a46cf759d265bb4e879d" integrity sha512-Wf+CseT/iOJjrGtAOf3ck9zS7AgPmr2fZ3N97r4+YXN3mBePTG2/bJ8DApl9mVwYL+RpYbNxMEkEp4mPGdwG/w== @@ -401,6 +539,32 @@ bech32 "1.1.4" ws "7.4.6" +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.2": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + "@ethersproject/random@5.6.1", "@ethersproject/random@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.6.1.tgz#66915943981bcd3e11bbd43733f5c3ba5a790255" @@ -409,6 +573,14 @@ "@ethersproject/bytes" "^5.6.1" "@ethersproject/logger" "^5.6.0" +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp@5.6.1", "@ethersproject/rlp@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.6.1.tgz#df8311e6f9f24dcb03d59a2bac457a28a4fe2bd8" @@ -417,6 +589,14 @@ "@ethersproject/bytes" "^5.6.1" "@ethersproject/logger" "^5.6.0" +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2@5.6.1", "@ethersproject/sha2@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.6.1.tgz#211f14d3f5da5301c8972a8827770b6fd3e51656" @@ -426,6 +606,15 @@ "@ethersproject/logger" "^5.6.0" hash.js "1.1.7" +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + "@ethersproject/signing-key@5.6.2", "@ethersproject/signing-key@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.6.2.tgz#8a51b111e4d62e5a62aee1da1e088d12de0614a3" @@ -438,6 +627,18 @@ elliptic "6.5.4" hash.js "1.1.7" +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + "@ethersproject/solidity@5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.6.1.tgz#5845e71182c66d32e6ec5eefd041fca091a473e2" @@ -450,6 +651,18 @@ "@ethersproject/sha2" "^5.6.1" "@ethersproject/strings" "^5.6.1" +"@ethersproject/solidity@5.7.0", "@ethersproject/solidity@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/strings@5.6.1", "@ethersproject/strings@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.6.1.tgz#dbc1b7f901db822b5cafd4ebf01ca93c373f8952" @@ -459,6 +672,15 @@ "@ethersproject/constants" "^5.6.1" "@ethersproject/logger" "^5.6.0" +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/transactions@5.6.2", "@ethersproject/transactions@^5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.6.2.tgz#793a774c01ced9fe7073985bb95a4b4e57a6370b" @@ -474,6 +696,21 @@ "@ethersproject/rlp" "^5.6.1" "@ethersproject/signing-key" "^5.6.2" +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/units@5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.6.1.tgz#ecc590d16d37c8f9ef4e89e2005bda7ddc6a4e6f" @@ -483,6 +720,15 @@ "@ethersproject/constants" "^5.6.1" "@ethersproject/logger" "^5.6.0" +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/wallet@5.6.2": version "5.6.2" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.6.2.tgz#cd61429d1e934681e413f4bc847a5f2f87e3a03c" @@ -504,6 +750,27 @@ "@ethersproject/transactions" "^5.6.2" "@ethersproject/wordlists" "^5.6.1" +"@ethersproject/wallet@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + "@ethersproject/web@5.6.1", "@ethersproject/web@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.6.1.tgz#6e2bd3ebadd033e6fe57d072db2b69ad2c9bdf5d" @@ -515,6 +782,17 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.1" +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/wordlists@5.6.1", "@ethersproject/wordlists@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.6.1.tgz#1e78e2740a8a21e9e99947e47979d72e130aeda1" @@ -526,6 +804,24 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.1" +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@gnosis.pm/safe-deployments@1.19.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-deployments/-/safe-deployments-1.19.0.tgz#f4ba8cf92cd6fdff4241ac50e410b4a6ff89babe" + integrity sha512-EvHR/LjMwJm0QKXyTscRXqR9vnJwCUDiMnRNKRyXe1akW+udiYXjJTAiGuleFS4DOUSqs6DpAjYlLnXMzUsVMw== + dependencies: + semver "^7.3.7" + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -691,6 +987,138 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nomicfoundation/ethereumjs-block@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz#fdd5c045e7baa5169abeed0e1202bf94e4481c49" + integrity sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA== + dependencies: + "@nomicfoundation/ethereumjs-common" "^3.0.0" + "@nomicfoundation/ethereumjs-rlp" "^4.0.0" + "@nomicfoundation/ethereumjs-trie" "^5.0.0" + "@nomicfoundation/ethereumjs-tx" "^4.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + ethereum-cryptography "0.1.3" + +"@nomicfoundation/ethereumjs-blockchain@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz#1a8c243a46d4d3691631f139bfb3a4a157187b0c" + integrity sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw== + dependencies: + "@nomicfoundation/ethereumjs-block" "^4.0.0" + "@nomicfoundation/ethereumjs-common" "^3.0.0" + "@nomicfoundation/ethereumjs-ethash" "^2.0.0" + "@nomicfoundation/ethereumjs-rlp" "^4.0.0" + "@nomicfoundation/ethereumjs-trie" "^5.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + abstract-level "^1.0.3" + debug "^4.3.3" + ethereum-cryptography "0.1.3" + level "^8.0.0" + lru-cache "^5.1.1" + memory-level "^1.0.0" + +"@nomicfoundation/ethereumjs-common@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz#f6bcc7753994555e49ab3aa517fc8bcf89c280b9" + integrity sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA== + dependencies: + "@nomicfoundation/ethereumjs-util" "^8.0.0" + crc-32 "^1.2.0" + +"@nomicfoundation/ethereumjs-ethash@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz#11539c32fe0990e1122ff987d1b84cfa34774e81" + integrity sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew== + dependencies: + "@nomicfoundation/ethereumjs-block" "^4.0.0" + "@nomicfoundation/ethereumjs-rlp" "^4.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + abstract-level "^1.0.3" + bigint-crypto-utils "^3.0.23" + ethereum-cryptography "0.1.3" + +"@nomicfoundation/ethereumjs-evm@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz#99cd173c03b59107c156a69c5e215409098a370b" + integrity sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q== + dependencies: + "@nomicfoundation/ethereumjs-common" "^3.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@types/async-eventemitter" "^0.2.1" + async-eventemitter "^0.2.4" + debug "^4.3.3" + ethereum-cryptography "0.1.3" + mcl-wasm "^0.7.1" + rustbn.js "~0.2.0" + +"@nomicfoundation/ethereumjs-rlp@^4.0.0", "@nomicfoundation/ethereumjs-rlp@^4.0.0-beta.2": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz#d9a9c5f0f10310c8849b6525101de455a53e771d" + integrity sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw== + +"@nomicfoundation/ethereumjs-statemanager@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz#14a9d4e1c828230368f7ab520c144c34d8721e4b" + integrity sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ== + dependencies: + "@nomicfoundation/ethereumjs-common" "^3.0.0" + "@nomicfoundation/ethereumjs-rlp" "^4.0.0" + "@nomicfoundation/ethereumjs-trie" "^5.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + debug "^4.3.3" + ethereum-cryptography "0.1.3" + functional-red-black-tree "^1.0.1" + +"@nomicfoundation/ethereumjs-trie@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz#dcfbe3be53a94bc061c9767a396c16702bc2f5b7" + integrity sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A== + dependencies: + "@nomicfoundation/ethereumjs-rlp" "^4.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + ethereum-cryptography "0.1.3" + readable-stream "^3.6.0" + +"@nomicfoundation/ethereumjs-tx@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz#59dc7452b0862b30342966f7052ab9a1f7802f52" + integrity sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w== + dependencies: + "@nomicfoundation/ethereumjs-common" "^3.0.0" + "@nomicfoundation/ethereumjs-rlp" "^4.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + ethereum-cryptography "0.1.3" + +"@nomicfoundation/ethereumjs-util@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz#deb2b15d2c308a731e82977aefc4e61ca0ece6c5" + integrity sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A== + dependencies: + "@nomicfoundation/ethereumjs-rlp" "^4.0.0-beta.2" + ethereum-cryptography "0.1.3" + +"@nomicfoundation/ethereumjs-vm@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz#2bb50d332bf41790b01a3767ffec3987585d1de6" + integrity sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w== + dependencies: + "@nomicfoundation/ethereumjs-block" "^4.0.0" + "@nomicfoundation/ethereumjs-blockchain" "^6.0.0" + "@nomicfoundation/ethereumjs-common" "^3.0.0" + "@nomicfoundation/ethereumjs-evm" "^1.0.0" + "@nomicfoundation/ethereumjs-rlp" "^4.0.0" + "@nomicfoundation/ethereumjs-statemanager" "^1.0.0" + "@nomicfoundation/ethereumjs-trie" "^5.0.0" + "@nomicfoundation/ethereumjs-tx" "^4.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@types/async-eventemitter" "^0.2.1" + async-eventemitter "^0.2.4" + debug "^4.3.3" + ethereum-cryptography "0.1.3" + functional-red-black-tree "^1.0.1" + mcl-wasm "^0.7.1" + rustbn.js "~0.2.0" + "@nomicfoundation/hardhat-chai-matchers@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.2.tgz#38720afbef25df20f0d11faf33cb63cdefb43059" @@ -703,22 +1131,88 @@ deep-eql "^4.0.1" ordinal "^1.0.3" -"@nomicfoundation/hardhat-network-helpers@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.3.tgz#3303cf7c8f52e1ef33ff4fc98d85af020d64f083" - integrity sha512-sBeUCzgrcdS8x7Nnr4t6wNpC7GIMMR3gQs8lVaxff5VWVKf7SjdkJ2rvSJsS2ckD8/8KGxeDTLb7XCOqVAjFjA== +"@nomicfoundation/hardhat-network-helpers@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.7.tgz#9103be2b359899a8b7996f54df12a1b7977367e3" + integrity sha512-X+3mNvn8B7BY5hpIaLO+TrfzWq12bpux+ajGGdmdcfC78NXmYmOZkAtiz1QZx1YIZGMS1LaXzPXyBExxKFpCaw== dependencies: ethereumjs-util "^7.1.4" -"@nomicfoundation/hardhat-toolbox@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-1.0.2.tgz#342b79e19c456a56d8e76bc2e9cc8474cbcfc774" - integrity sha512-8CEgWSKUK2aMit+76Sez8n7UB0Ze1lwT+LcWxj4EFP30lQWOwOws048t6MTPfThH0BlSWjC6hJRr0LncIkc1Sw== +"@nomicfoundation/hardhat-toolbox@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.1.tgz#3d8c620a54df2c0086c9109fb0f987bf29329deb" + integrity sha512-/pr8m9xlqiNlq6fXv4hEPNwdNwUhysoB2qbDCKqERfPpq34EydUQTC3Vis4aIea8RLwSrU8sDXFdv4TQxYstKw== -"@nomiclabs/hardhat-ethers@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.1.0.tgz#9b7dc94d669ad9dc286b94f6f2f1513118c7027b" - integrity sha512-vlW90etB3675QWG7tMrHaDoTa7ymMB7irM4DAQ98g8zJoe9YqEggeDnbO6v5b+BLth/ty4vN6Ko/kaqRN1krHw== +"@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.0.tgz#83a7367342bd053a76d04bbcf4f373fef07cf760" + integrity sha512-vEF3yKuuzfMHsZecHQcnkUrqm8mnTWfJeEVFHpg+cO+le96xQA4lAJYdUan8pXZohQxv1fSReQsn4QGNuBNuCw== + +"@nomicfoundation/solidity-analyzer-darwin-x64@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.0.tgz#1225f7da647ae1ad25a87125664704ecc0af6ccc" + integrity sha512-dlHeIg0pTL4dB1l9JDwbi/JG6dHQaU1xpDK+ugYO8eJ1kxx9Dh2isEUtA4d02cQAl22cjOHTvifAk96A+ItEHA== + +"@nomicfoundation/solidity-analyzer-freebsd-x64@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.0.tgz#dbc052dcdfd50ae50fd5ae1788b69b4e0fa40040" + integrity sha512-WFCZYMv86WowDA4GiJKnebMQRt3kCcFqHeIomW6NMyqiKqhK1kIZCxSLDYsxqlx396kKLPN1713Q1S8tu68GKg== + +"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.0.tgz#e6b2eea633995b557e74e881d2a43eab4760903d" + integrity sha512-DTw6MNQWWlCgc71Pq7CEhEqkb7fZnS7oly13pujs4cMH1sR0JzNk90Mp1zpSCsCs4oKan2ClhMlLKtNat/XRKQ== + +"@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.0.tgz#af81107f5afa794f19988a368647727806e18dc4" + integrity sha512-wUpUnR/3GV5Da88MhrxXh/lhb9kxh9V3Jya2NpBEhKDIRCDmtXMSqPMXHZmOR9DfCwCvG6vLFPr/+YrPCnUN0w== + +"@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.0.tgz#6877e1da1a06a9f08446070ab6e0a5347109f868" + integrity sha512-lR0AxK1x/MeKQ/3Pt923kPvwigmGX3OxeU5qNtQ9pj9iucgk4PzhbS3ruUeSpYhUxG50jN4RkIGwUMoev5lguw== + +"@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.0.tgz#bb6cd83a0c259eccef4183796b6329a66cf7ebd9" + integrity sha512-A1he/8gy/JeBD3FKvmI6WUJrGrI5uWJNr5Xb9WdV+DK0F8msuOqpEByLlnTdLkXMwW7nSl3awvLezOs9xBHJEg== + +"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.0.tgz#9d4bca1cc9a1333fde985675083b0b7d165f6076" + integrity sha512-7x5SXZ9R9H4SluJZZP8XPN+ju7Mx+XeUMWZw7ZAqkdhP5mK19I4vz3x0zIWygmfE8RT7uQ5xMap0/9NPsO+ykw== + +"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.0.tgz#0db5bfc6aa952bea4098d8d2c8947b4e5c4337ee" + integrity sha512-m7w3xf+hnE774YRXu+2mGV7RiF3QJtUoiYU61FascCkQhX3QMQavh7saH/vzb2jN5D24nT/jwvaHYX/MAM9zUw== + +"@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.0.tgz#2e0f39a2924dcd77db6b419828595e984fabcb33" + integrity sha512-xCuybjY0sLJQnJhupiFAXaek2EqF0AP0eBjgzaalPXSNvCEN6ZYHvUzdA50ENDVeSYFXcUsYf3+FsD3XKaeptA== + +"@nomicfoundation/solidity-analyzer@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.0.tgz#e5ddc43ad5c0aab96e5054520d8e16212e125f50" + integrity sha512-xGWAiVCGOycvGiP/qrlf9f9eOn7fpNbyJygcB0P21a1MDuVPlKt0Srp7rvtBEutYQ48ouYnRXm33zlRnlTOPHg== + optionalDependencies: + "@nomicfoundation/solidity-analyzer-darwin-arm64" "0.1.0" + "@nomicfoundation/solidity-analyzer-darwin-x64" "0.1.0" + "@nomicfoundation/solidity-analyzer-freebsd-x64" "0.1.0" + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu" "0.1.0" + "@nomicfoundation/solidity-analyzer-linux-arm64-musl" "0.1.0" + "@nomicfoundation/solidity-analyzer-linux-x64-gnu" "0.1.0" + "@nomicfoundation/solidity-analyzer-linux-x64-musl" "0.1.0" + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc" "0.1.0" + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.0" + "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.0" + +"@nomiclabs/hardhat-ethers@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.2.tgz#812d48929c3bf8fe840ec29eab4b613693467679" + integrity sha512-NLDlDFL2us07C0jB/9wzvR0kuLivChJWCXTKcj3yqjZqMoYp7g7wwS157F70VHx/+9gHIBGzak5pKDwG8gEefA== "@nomiclabs/hardhat-etherscan@^3.1.0": version "3.1.0" @@ -788,6 +1282,47 @@ web3 "^1.2.5" web3-utils "^1.2.5" +"@safe-global/safe-core-sdk-types@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@safe-global/safe-core-sdk-types/-/safe-core-sdk-types-1.9.0.tgz#dba18f34ab905da6c1c03a3262aeb47cc0bead14" + integrity sha512-3VFhnggdLT9kWhnb35eqxEC9cVpW5Sl4E51TZo0RmH1cSzMwlNDbeGINb38PpUbw/wwXFJbCVFmeXELz2ZlCfw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@gnosis.pm/safe-deployments" "1.19.0" + web3-core "^1.8.1" + web3-utils "^1.8.1" + +"@safe-global/safe-core-sdk-utils@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@safe-global/safe-core-sdk-utils/-/safe-core-sdk-utils-1.7.1.tgz#45a9d5ea00a0aa3c74a71114f30f71d279a0872a" + integrity sha512-lztn4GeqKMWN3n/NB2LXdqv9FQdUbFOrmkpJoOhMu/jdPgQhvptpVc4pjKEMvVfbHe3aNkbLj/dV7LbTenbkWQ== + dependencies: + "@safe-global/safe-core-sdk-types" "^1.9.0" + semver "^7.3.8" + web3-utils "^1.8.1" + +"@safe-global/safe-core-sdk@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@safe-global/safe-core-sdk/-/safe-core-sdk-3.3.1.tgz#3cfcb143cab9ac6d42388046aefdcfea7434503b" + integrity sha512-smuVdFQIAifC79K2maCY4w1pCYWvXIbrPftKQSC+hxhWmDjJYsRfhbBT2lSg2lYbiXeosFGEPMVbiVjtnjQg5w== + dependencies: + "@ethersproject/solidity" "^5.7.0" + "@gnosis.pm/safe-deployments" "1.19.0" + "@safe-global/safe-core-sdk-types" "^1.9.0" + "@safe-global/safe-core-sdk-utils" "^1.7.1" + ethereumjs-util "^7.1.5" + semver "^7.3.8" + web3-utils "^1.8.1" + +"@safe-global/safe-ethers-lib@https://github.com/MisterMard/safe-core-sdk/raw/release/patched/packages/safe-ethers-lib/safe-global-safe-ethers-lib-1.9.2.tgz": + version "1.9.2" + resolved "https://github.com/MisterMard/safe-core-sdk/raw/release/patched/packages/safe-ethers-lib/safe-global-safe-ethers-lib-1.9.2.tgz#233abe482c6d9c9c5d44ee89b979ba28c033ce4a" + dependencies: + "@safe-global/safe-core-sdk-types" "^1.9.0" + "@safe-global/safe-core-sdk-utils" "^1.7.1" + ethers "5.7.2" + "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" @@ -883,7 +1418,7 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.2", "@solidity-parser/parser@^0.14.3": +"@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.3": version "0.14.3" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.3.tgz#0d627427b35a40d8521aaa933cc3df7d07bfa36f" integrity sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw== @@ -1033,26 +1568,25 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@typechain/ethers-v5@^10.1.0": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.1.0.tgz#068d7dc7014502354696dab59590a7841091e951" - integrity sha512-3LIb+eUpV3mNCrjUKT5oqp8PBsZYSnVrkfk6pY/ZM0boRs2mKxjFZ7bktx42vfDye8PPz3NxtW4DL5NsNsFqlg== +"@typechain/ethers-v5@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.1.1.tgz#fdb527d8854129cea5f139d76c6c6e1c9bb040ec" + integrity sha512-o6nffJBxwmeX1ZiZpdnP/tqGd/7M7iYvQC88ZXaFFoyAGh7eYncynzVjOJV0XmaKzAc6puqyqZrnva+gJbk4sw== dependencies: lodash "^4.17.15" ts-essentials "^7.0.1" -"@typechain/hardhat@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.2.tgz#d3beccc6937d93f9b437616b741f839a8b953693" - integrity sha512-k4Ea3pVITKB2DH8p1a5U38cyy7KZPD04Spo4q5b4wO+n2mT+uAz5dxckPtbczn/Kk5wiFq+ZkuOtw5ZKFhL/+w== +"@typechain/hardhat@^6.1.4": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.4.tgz#da930bf17bdae5e0996b86d37992c6c58b8a49c8" + integrity sha512-S8k5d1Rjc+plwKpkorlifmh72M7Ki0XNUOVVLtdbcA/vLaEkuqZSJFdddpBgS5QxiJP+6CbRa/yO6EVTE2+fMQ== dependencies: fs-extra "^9.1.0" - lodash "^4.17.15" -"@types/abstract-leveldown@*": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz#f055979a99f7654e84d6b8e6267419e9c4cfff87" - integrity sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ== +"@types/async-eventemitter@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz#f8e6280e87e8c60b2b938624b0a3530fb3e24712" + integrity sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg== "@types/bignumber.js@^5.0.0": version "5.0.0" @@ -1109,20 +1643,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/level-errors@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/level-errors/-/level-errors-3.0.0.tgz#15c1f4915a5ef763b51651b15e90f6dc081b96a8" - integrity sha512-/lMtoq/Cf/2DVOm6zE6ORyOM+3ZVm/BvzEZVxUhf6bgh8ZHglXlBqxbxSlJeVp8FCbD3IVvk/VbsaNmDjrQvqQ== - -"@types/levelup@^4.3.0": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@types/levelup/-/levelup-4.3.3.tgz#4dc2b77db079b1cf855562ad52321aa4241b8ef4" - integrity sha512-K+OTIjJcZHVlZQN1HmU64VtrC0jC3dXWQozuEIR9zVvltIk90zaGPM2AgT+fIkChpzHhFE3YnvFLCbLtzAmexA== - dependencies: - "@types/abstract-leveldown" "*" - "@types/level-errors" "*" - "@types/node" "*" - "@types/lru-cache@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" @@ -1204,27 +1724,23 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -abstract-leveldown@^6.2.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz#d25221d1e6612f820c35963ba4bd739928f6026a" - integrity sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ== - dependencies: - buffer "^5.5.0" - immediate "^3.2.3" - level-concat-iterator "~2.0.0" - level-supports "~1.0.0" - xtend "~4.0.0" +abortcontroller-polyfill@^1.7.3: + version "1.7.5" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed" + integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ== -abstract-leveldown@~6.2.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz#036543d87e3710f2528e47040bc3261b77a9a8eb" - integrity sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ== +abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" + integrity sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA== dependencies: - buffer "^5.5.0" - immediate "^3.2.3" - level-concat-iterator "~2.0.0" - level-supports "~1.0.0" - xtend "~4.0.0" + buffer "^6.0.3" + catering "^2.1.0" + is-buffer "^2.0.5" + level-supports "^4.0.0" + level-transcoder "^1.0.1" + module-error "^1.0.1" + queue-microtask "^1.2.3" accepts@~1.3.8: version "1.3.8" @@ -1581,6 +2097,18 @@ big.js@^6.0.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.1.tgz#7205ce763efb17c2e41f26f121c420c6a7c2744f" integrity sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ== +bigint-crypto-utils@^3.0.23: + version "3.1.7" + resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.1.7.tgz#c4c1b537c7c1ab7aadfaecf3edfd45416bf2c651" + integrity sha512-zpCQpIE2Oy5WIQpjC9iYZf8Uh9QqoS51ZCooAcNvzv1AQ3VWdT52D0ksr1+/faeK8HVIej1bxXcP75YcqH3KPA== + dependencies: + bigint-mod-arith "^3.1.0" + +bigint-mod-arith@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz#658e416bc593a463d97b59766226d0a3021a76b1" + integrity sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ== + bignumber.js@*, bignumber.js@^9.0.0, bignumber.js@^9.0.1: version "9.0.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673" @@ -1627,7 +2155,7 @@ bn.js@4.11.6: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.2.1: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== @@ -1687,6 +2215,16 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +browser-level@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browser-level/-/browser-level-1.0.1.tgz#36e8c3183d0fe1c405239792faaab5f315871011" + integrity sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ== + dependencies: + abstract-level "^1.0.2" + catering "^2.1.1" + module-error "^1.0.2" + run-parallel-limit "^1.1.0" + browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -1777,14 +2315,7 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== -buffer-xor@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-2.0.2.tgz#34f7c64f04c777a1f8aac5e661273bb9dd320289" - integrity sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ== - dependencies: - safe-buffer "^5.1.1" - -buffer@6.0.3: +buffer@6.0.3, buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -1807,6 +2338,13 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -1871,6 +2409,11 @@ caseless@^0.12.0, caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +catering@^2.1.0, catering@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" + integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== + cbor@^5.0.2, cbor@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/cbor/-/cbor-5.2.0.tgz#4cca67783ccd6de7b50ab4ed62636712f287a67c" @@ -2049,6 +2592,17 @@ class-is@^1.1.0: resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== +classic-level@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/classic-level/-/classic-level-1.2.0.tgz#2d52bdec8e7a27f534e67fdeb890abef3e643c27" + integrity sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg== + dependencies: + abstract-level "^1.0.2" + catering "^2.1.0" + module-error "^1.0.1" + napi-macros "~2.0.0" + node-gyp-build "^4.3.0" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -2269,11 +2823,6 @@ cookiejar@^2.1.1: resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== -core-js-pure@^3.0.1: - version "3.24.1" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.1.tgz#8839dde5da545521bf282feb7dc6d0b425f39fd3" - integrity sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg== - core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2333,6 +2882,13 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-fetch@^3.1.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2506,14 +3062,6 @@ defer-to-connect@^1.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== -deferred-leveldown@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz#27a997ad95408b61161aa69bd489b86c71b78058" - integrity sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw== - dependencies: - abstract-leveldown "~6.2.1" - inherits "^2.0.3" - define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" @@ -2709,16 +3257,6 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -encoding-down@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/encoding-down/-/encoding-down-6.3.0.tgz#b1c4eb0e1728c146ecaef8e32963c549e76d082b" - integrity sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw== - dependencies: - abstract-leveldown "^6.2.1" - inherits "^2.0.3" - level-codec "^9.0.0" - level-errors "^2.0.0" - end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -2743,13 +3281,6 @@ env-paths@^2.2.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== -errno@~0.1.1: - version "0.1.8" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" - integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== - dependencies: - prr "~1.0.1" - error-ex@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2818,6 +3349,11 @@ es6-iterator@^2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" +es6-promise@^4.2.8: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" @@ -3044,7 +3580,7 @@ ethereum-bloom-filters@^1.0.6: dependencies: js-sha3 "^0.8.0" -ethereum-cryptography@^0.1.3: +ethereum-cryptography@0.1.3, ethereum-cryptography@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== @@ -3096,7 +3632,7 @@ ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1: ethjs-util "0.1.6" rlp "^2.2.3" -ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.4, ethereumjs-util@^7.1.5: +ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.4, ethereumjs-util@^7.1.5: version "7.1.5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== @@ -3107,6 +3643,42 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereu ethereum-cryptography "^0.1.3" rlp "^2.2.4" +ethers@5.7.2, ethers@^5.7.0, ethers@^5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + ethers@^4.0.32, ethers@^4.0.40: version "4.0.49" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" @@ -3122,7 +3694,7 @@ ethers@^4.0.32, ethers@^4.0.40: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.0.13, ethers@^5.1.3, ethers@^5.5.3, ethers@^5.6.8: +ethers@^5.0.13, ethers@^5.5.3: version "5.6.9" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.9.tgz#4e12f8dfcb67b88ae7a78a9519b384c23c576a4d" integrity sha512-lMGC2zv9HC5EC+8r429WaWu3uWJUCgUCt8xxKCFqkrFuBDZXDYIdzDUECxzjf2BMF8IVBByY1EBoGSL3RTm8RA== @@ -3608,7 +4180,7 @@ function.prototype.name@^1.1.5: es-abstract "^1.19.0" functions-have-names "^1.2.2" -functional-red-black-tree@^1.0.1, functional-red-black-tree@~1.0.1: +functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== @@ -3899,10 +4471,10 @@ hardhat-contract-sizer@^2.1.1: chalk "^4.0.0" cli-table3 "^0.6.0" -hardhat-deploy@^0.11.12: - version "0.11.12" - resolved "https://registry.yarnpkg.com/hardhat-deploy/-/hardhat-deploy-0.11.12.tgz#323e05ecd8e6c80a9193b9c6f6c8ecbb6abfbe47" - integrity sha512-Wv0BqzwW4mz78raxkfQtBbkhZwZTRWXwRbEwgkTUimD3MX/0Z9+D4O+s1zHJlG0zwZvJMbwxPOrOHQm4NZ3JAQ== +hardhat-deploy@^0.11.22: + version "0.11.23" + resolved "https://registry.yarnpkg.com/hardhat-deploy/-/hardhat-deploy-0.11.23.tgz#7c5d11ba32acfd7c5730490bf6af8815b435c996" + integrity sha512-9F+sDRX79D/oV1cUEE0k2h5LiccrnzXEtrMofL5PTVDCJfUnRvhQqCRi4NhcYmxf2+MBkOIJv5KyzP0lz6ojTw== dependencies: "@types/qs" "^6.9.7" axios "^0.21.1" @@ -3916,7 +4488,7 @@ hardhat-deploy@^0.11.12: match-all "^1.2.6" murmur-128 "^0.2.1" qs "^6.9.4" - zksync-web3 "^0.7.8" + zksync-web3 "^0.8.1" hardhat-gas-reporter@^1.0.4: version "1.0.8" @@ -3934,20 +4506,25 @@ hardhat-preprocessor@^0.1.4: dependencies: murmur-128 "^0.2.1" -"hardhat@npm:@gzeoneth/hardhat@2.10.2-gzeon-e972f1dda": - version "2.10.2-gzeon-e972f1dda" - resolved "https://registry.yarnpkg.com/@gzeoneth/hardhat/-/hardhat-2.10.2-gzeon-e972f1dda.tgz#33d3b7dc9acfb13edd735d0633788cb91ea5dc0f" - integrity sha512-QcdLZNyYVzMkVsDEXzS+/9SrZklZTfUIxV/oxtnroe7a70QReB+truuHn6MqkAsAmAhI/OXcoAZkOaSJHrDUMQ== +hardhat@^2.12.6: + version "2.12.6" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.12.6.tgz#ea3c058bbd81850867389d10f76037cfa52a0019" + integrity sha512-0Ent1O5DsPgvaVb5sxEgsQ3bJRt/Ex92tsoO+xjoNH2Qc4bFmhI5/CHVlFikulalxOPjNmw5XQ2vJFuVQFESAA== dependencies: - "@ethereumjs/block" "^3.6.2" - "@ethereumjs/blockchain" "^5.5.2" - "@ethereumjs/common" "^2.6.4" - "@ethereumjs/tx" "^3.5.1" - "@ethereumjs/vm" "^5.9.0" "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" + "@nomicfoundation/ethereumjs-block" "^4.0.0" + "@nomicfoundation/ethereumjs-blockchain" "^6.0.0" + "@nomicfoundation/ethereumjs-common" "^3.0.0" + "@nomicfoundation/ethereumjs-evm" "^1.0.0" + "@nomicfoundation/ethereumjs-rlp" "^4.0.0" + "@nomicfoundation/ethereumjs-statemanager" "^1.0.0" + "@nomicfoundation/ethereumjs-trie" "^5.0.0" + "@nomicfoundation/ethereumjs-tx" "^4.0.0" + "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-vm" "^6.0.0" + "@nomicfoundation/solidity-analyzer" "^0.1.0" "@sentry/node" "^5.18.1" - "@solidity-parser/parser" "^0.14.2" "@types/bn.js" "^5.1.0" "@types/lru-cache" "^5.1.0" abort-controller "^3.0.0" @@ -3962,15 +4539,14 @@ hardhat-preprocessor@^0.1.4: env-paths "^2.2.0" ethereum-cryptography "^1.0.3" ethereumjs-abi "^0.6.8" - ethereumjs-util "^7.1.4" find-up "^2.1.0" fp-ts "1.19.3" fs-extra "^7.0.1" glob "7.2.0" immutable "^4.0.0-rc.12" io-ts "1.10.4" + keccak "^3.0.2" lodash "^4.17.11" - merkle-patricia-tree "^4.2.4" mnemonist "^0.38.0" mocha "^10.0.0" p-map "^4.0.0" @@ -3978,13 +4554,11 @@ hardhat-preprocessor@^0.1.4: raw-body "^2.4.1" resolve "1.17.0" semver "^6.3.0" - slash "^3.0.0" solc "0.7.3" source-map-support "^0.5.13" stacktrace-parser "^0.1.10" - "true-case-path" "^2.2.1" tsort "0.0.1" - undici "^5.4.0" + undici "^5.14.0" uuid "^8.3.2" ws "^7.4.6" @@ -4207,16 +4781,6 @@ ignore@^5.1.1: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -immediate@^3.2.3: - version "3.3.0" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" - integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== - -immediate@~3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" - integrity sha512-RrGCXRm/fRVqMIhqXrGEX9rRADavPiDFSoMb/k64i9XMk8uH4r/Omi5Ctierj6XzNecwDbO4WuFbDD1zmpl3Tg== - immutable@^4.0.0-rc.12: version "4.1.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" @@ -4253,7 +4817,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4355,7 +4919,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@~2.0.3: +is-buffer@^2.0.5, is-buffer@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== @@ -4687,7 +5251,7 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" -keccak@^3.0.0: +keccak@^3.0.0, keccak@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" integrity sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ== @@ -4722,76 +5286,26 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -level-codec@^9.0.0: - version "9.0.2" - resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.2.tgz#fd60df8c64786a80d44e63423096ffead63d8cbc" - integrity sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ== - dependencies: - buffer "^5.6.0" - -level-concat-iterator@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz#1d1009cf108340252cb38c51f9727311193e6263" - integrity sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw== - -level-errors@^2.0.0, level-errors@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-2.0.1.tgz#2132a677bf4e679ce029f517c2f17432800c05c8" - integrity sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw== - dependencies: - errno "~0.1.1" - -level-iterator-stream@~4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz#7ceba69b713b0d7e22fcc0d1f128ccdc8a24f79c" - integrity sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q== - dependencies: - inherits "^2.0.4" - readable-stream "^3.4.0" - xtend "^4.0.2" - -level-mem@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/level-mem/-/level-mem-5.0.1.tgz#c345126b74f5b8aa376dc77d36813a177ef8251d" - integrity sha512-qd+qUJHXsGSFoHTziptAKXoLX87QjR7v2KMbqncDXPxQuCdsQlzmyX+gwrEHhlzn08vkf8TyipYyMmiC6Gobzg== - dependencies: - level-packager "^5.0.3" - memdown "^5.0.0" - -level-packager@^5.0.3: - version "5.1.1" - resolved "https://registry.yarnpkg.com/level-packager/-/level-packager-5.1.1.tgz#323ec842d6babe7336f70299c14df2e329c18939" - integrity sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ== - dependencies: - encoding-down "^6.3.0" - levelup "^4.3.2" +level-supports@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-4.0.1.tgz#431546f9d81f10ff0fea0e74533a0e875c08c66a" + integrity sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA== -level-supports@~1.0.0: +level-transcoder@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-1.0.1.tgz#2f530a596834c7301622521988e2c36bb77d122d" - integrity sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg== - dependencies: - xtend "^4.0.2" - -level-ws@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/level-ws/-/level-ws-2.0.0.tgz#207a07bcd0164a0ec5d62c304b4615c54436d339" - integrity sha512-1iv7VXx0G9ec1isqQZ7y5LmoZo/ewAsyDHNA8EFDW5hqH2Kqovm33nSFkSdnLLAK+I5FlT+lo5Cw9itGe+CpQA== + resolved "https://registry.yarnpkg.com/level-transcoder/-/level-transcoder-1.0.1.tgz#f8cef5990c4f1283d4c86d949e73631b0bc8ba9c" + integrity sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w== dependencies: - inherits "^2.0.3" - readable-stream "^3.1.0" - xtend "^4.0.1" + buffer "^6.0.3" + module-error "^1.0.1" -levelup@^4.3.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/levelup/-/levelup-4.4.0.tgz#f89da3a228c38deb49c48f88a70fb71f01cafed6" - integrity sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ== +level@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/level/-/level-8.0.0.tgz#41b4c515dabe28212a3e881b61c161ffead14394" + integrity sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ== dependencies: - deferred-leveldown "~5.3.0" - level-errors "~2.0.0" - level-iterator-stream "~4.0.0" - level-supports "~1.0.0" - xtend "~4.0.0" + browser-level "^1.0.1" + classic-level "^1.2.0" levn@^0.4.1: version "0.4.1" @@ -4950,11 +5464,6 @@ lru_map@^0.3.3: resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== -ltgt@~2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" - integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== - make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -4989,17 +5498,14 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -memdown@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/memdown/-/memdown-5.1.0.tgz#608e91a9f10f37f5b5fe767667a8674129a833cb" - integrity sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw== - dependencies: - abstract-leveldown "~6.2.1" - functional-red-black-tree "~1.0.1" - immediate "~3.2.3" - inherits "~2.0.1" - ltgt "~2.2.0" - safe-buffer "~5.2.0" +memory-level@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/memory-level/-/memory-level-1.0.0.tgz#7323c3fd368f9af2f71c3cd76ba403a17ac41692" + integrity sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og== + dependencies: + abstract-level "^1.0.0" + functional-red-black-tree "^1.0.1" + module-error "^1.0.1" memorystream@^0.3.1: version "0.3.1" @@ -5016,18 +5522,6 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -merkle-patricia-tree@^4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-4.2.4.tgz#ff988d045e2bf3dfa2239f7fabe2d59618d57413" - integrity sha512-eHbf/BG6eGNsqqfbLED9rIqbsF4+sykEaBn6OLNs71tjclbMcMOk1tEPmJKcNcNCLkvbpY/lwyOlizWsqPNo8w== - dependencies: - "@types/levelup" "^4.3.0" - ethereumjs-util "^7.1.4" - level-mem "^5.0.1" - level-ws "^2.0.0" - readable-stream "^3.6.0" - semaphore-async-await "^1.5.1" - methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -5277,6 +5771,11 @@ mock-fs@^4.1.0: resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== +module-error@^1.0.1, module-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" + integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5381,6 +5880,11 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== +napi-macros@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" + integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -5445,7 +5949,7 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@^2.6.1: +node-fetch@2.6.7, node-fetch@^2.6.1: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -6053,11 +6557,6 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== - psl@^1.1.28: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -6126,7 +6625,7 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -queue-microtask@^1.2.2: +queue-microtask@^1.2.2, queue-microtask@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== @@ -6201,7 +6700,7 @@ readable-stream@^2.0.6, readable-stream@^2.2.2: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -6433,6 +6932,13 @@ run-async@^2.4.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-parallel-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz#be80e936f5768623a38a963262d6bef8ff11e7ba" + integrity sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw== + dependencies: + queue-microtask "^1.2.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -6506,11 +7012,6 @@ secp256k1@^4.0.1: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -semaphore-async-await@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz#857bef5e3644601ca4b9570b87e9df5ca12974fa" - integrity sha512-b/ptP11hETwYWpeilHXXQiV5UJNJl7ZWWooKRE5eBIYWoom6dZ0SluCIdCtKycsMtZgKWE01/qAw6jblw1YVhg== - "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -6528,6 +7029,13 @@ semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -6843,6 +7351,11 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g== +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -7195,11 +7708,6 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -"true-case-path@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" - integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== - ts-command-line-args@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.3.1.tgz#b6188e42efc6cf7a8898e438a873fbb15505ddd6" @@ -7318,10 +7826,10 @@ type@^2.5.0: resolved "https://registry.yarnpkg.com/type/-/type-2.6.1.tgz#808f389ec777205cc3cd97c1c88ec1a913105aae" integrity sha512-OvgH5rB0XM+iDZGQ1Eg/o7IZn0XYJFVrN/9FQ4OWIYILyJJgVP2s1hLTOFn6UOZoDUI/HctGa0PFlE2n2HW3NQ== -typechain@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.1.0.tgz#fc4902ce596519cb2ccfd012e4ddf92a9945b569" - integrity sha512-5jToLgKTjHdI1VKqs/K8BLYy42Sr3o8bV5ojh4MnR9ExHO83cyyUdw+7+vMJCpKXUiVUvARM4qmHTFuyaCMAZQ== +typechain@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.1.1.tgz#9c2e8012c2c4c586536fc18402dcd7034c4ff0bd" + integrity sha512-uF/sUvnXTOVF2FHKhQYnxHk4su4JjZR8vr4mA2mBaRwHTbwh0jIlqARz9XJr1tA0l7afJGvEa1dTSi4zt039LQ== dependencies: "@types/prettier" "^2.1.1" debug "^4.3.1" @@ -7386,6 +7894,13 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici@^5.14.0: + version "5.18.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.18.0.tgz#e88a77a74d991a30701e9a6751e4193a26fabda9" + integrity sha512-1iVwbhonhFytNdg0P4PqyIAXbdlVZVebtPDvuM36m66mRw4OGrCm2MYynJv/UENFLdP13J1nPVQzVE2zTs1OeA== + dependencies: + busboy "^1.6.0" + undici@^5.4.0: version "5.8.0" resolved "https://registry.yarnpkg.com/undici/-/undici-5.8.0.tgz#dec9a8ccd90e5a1d81d43c0eab6503146d649a4f" @@ -7486,6 +8001,17 @@ util@^0.12.0: safe-buffer "^5.1.2" which-typed-array "^1.1.2" +util@^0.12.5: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -7572,6 +8098,14 @@ web3-core-helpers@1.7.4: web3-eth-iban "1.7.4" web3-utils "1.7.4" +web3-core-helpers@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz#82066560f8085e6c7b93bcc8e88b441289ea9f9f" + integrity sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw== + dependencies: + web3-eth-iban "1.8.2" + web3-utils "1.8.2" + web3-core-method@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.7.4.tgz#3873c6405e1a0a8a1efc1d7b28de8b7550b00c15" @@ -7583,6 +8117,17 @@ web3-core-method@1.7.4: web3-core-subscriptions "1.7.4" web3-utils "1.7.4" +web3-core-method@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.8.2.tgz#ba5ec68084e903f0516415010477618be017eac2" + integrity sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA== + dependencies: + "@ethersproject/transactions" "^5.6.2" + web3-core-helpers "1.8.2" + web3-core-promievent "1.8.2" + web3-core-subscriptions "1.8.2" + web3-utils "1.8.2" + web3-core-promievent@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.7.4.tgz#80a75633fdfe21fbaae2f1e38950edb2f134868c" @@ -7590,6 +8135,13 @@ web3-core-promievent@1.7.4: dependencies: eventemitter3 "4.0.4" +web3-core-promievent@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz#e670d6b4453632e6ecfd9ad82da44f77ac1585c9" + integrity sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg== + dependencies: + eventemitter3 "4.0.4" + web3-core-requestmanager@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.7.4.tgz#2dc8a526dab8183dca3fef54658621801b1d0469" @@ -7601,6 +8153,17 @@ web3-core-requestmanager@1.7.4: web3-providers-ipc "1.7.4" web3-providers-ws "1.7.4" +web3-core-requestmanager@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz#dda95e83ca4808949612a41e54ecea557f78ef26" + integrity sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g== + dependencies: + util "^0.12.5" + web3-core-helpers "1.8.2" + web3-providers-http "1.8.2" + web3-providers-ipc "1.8.2" + web3-providers-ws "1.8.2" + web3-core-subscriptions@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.7.4.tgz#cfbd3fa71081a8c8c6f1a64577a1a80c5bd9826f" @@ -7609,6 +8172,14 @@ web3-core-subscriptions@1.7.4: eventemitter3 "4.0.4" web3-core-helpers "1.7.4" +web3-core-subscriptions@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz#0c8bd49439d83c6f0a03c70f00b24a915a70a5ed" + integrity sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.8.2" + web3-core@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.7.4.tgz#943fff99134baedafa7c65b4a0bbd424748429ff" @@ -7622,6 +8193,19 @@ web3-core@1.7.4: web3-core-requestmanager "1.7.4" web3-utils "1.7.4" +web3-core@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.8.2.tgz#333e93d7872b1a36efe758ed8b89a7acbdd962c2" + integrity sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ== + dependencies: + "@types/bn.js" "^5.1.0" + "@types/node" "^12.12.6" + bignumber.js "^9.0.0" + web3-core-helpers "1.8.2" + web3-core-method "1.8.2" + web3-core-requestmanager "1.8.2" + web3-utils "1.8.2" + web3-eth-abi@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.7.4.tgz#3fee967bafd67f06b99ceaddc47ab0970f2a614a" @@ -7683,6 +8267,14 @@ web3-eth-iban@1.7.4: bn.js "^5.2.1" web3-utils "1.7.4" +web3-eth-iban@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz#5cb3022234b13986f086353b53f0379a881feeaf" + integrity sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ== + dependencies: + bn.js "^5.2.1" + web3-utils "1.8.2" + web3-eth-personal@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.7.4.tgz#22c399794cb828a75703df8bb4b3c1331b471546" @@ -7730,6 +8322,16 @@ web3-providers-http@1.7.4: web3-core-helpers "1.7.4" xhr2-cookies "1.1.0" +web3-providers-http@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.8.2.tgz#fbda3a3bbc8db004af36e91bec35f80273b37885" + integrity sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ== + dependencies: + abortcontroller-polyfill "^1.7.3" + cross-fetch "^3.1.4" + es6-promise "^4.2.8" + web3-core-helpers "1.8.2" + web3-providers-ipc@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.7.4.tgz#02e85e99e48f432c9d34cee7d786c3685ec9fcfa" @@ -7738,6 +8340,14 @@ web3-providers-ipc@1.7.4: oboe "2.1.5" web3-core-helpers "1.7.4" +web3-providers-ipc@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz#e52a7250f40c83b99a2482ec5b4cf2728377ae5c" + integrity sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w== + dependencies: + oboe "2.1.5" + web3-core-helpers "1.8.2" + web3-providers-ws@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.7.4.tgz#6e60bcefb456f569a3e766e386d7807a96f90595" @@ -7747,6 +8357,15 @@ web3-providers-ws@1.7.4: web3-core-helpers "1.7.4" websocket "^1.0.32" +web3-providers-ws@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz#56a2b701387011aca9154ca4bc06ea4b5f27e4ef" + integrity sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.8.2" + websocket "^1.0.32" + web3-shh@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.7.4.tgz#bee91cce2737c529fd347274010b548b6ea060f1" @@ -7770,6 +8389,19 @@ web3-utils@1.7.4, web3-utils@^1.0.0-beta.31, web3-utils@^1.2.5, web3-utils@^1.3. randombytes "^2.1.0" utf8 "3.0.0" +web3-utils@1.8.2, web3-utils@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.8.2.tgz#c32dec5e9b955acbab220eefd7715bc540b75cc9" + integrity sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA== + dependencies: + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereumjs-util "^7.1.0" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + web3@1.7.4, web3@^1.2.5, web3@^1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/web3/-/web3-1.7.4.tgz#00c9aef8e13ade92fd773d845fff250535828e93" @@ -7999,7 +8631,7 @@ xmlhttprequest@1.8.0: resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== -xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0: +xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== @@ -8138,7 +8770,7 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zksync-web3@^0.7.8: - version "0.7.11" - resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.7.11.tgz#1d829eda9b220b94a3d7e3ab29a2b37ab789a276" - integrity sha512-CdIHw+8BirGnYCXGIhF6NL7Ge30k/Sdvm8vmN/cV7WRaLcSOEYVfb9B7uYpcuzAwR44YTZ8yv1izcKCa1EGOtg== +zksync-web3@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.8.1.tgz#db289d8f6caf61f4d5ddc471fa3448d93208dc14" + integrity sha512-1A4aHPQ3MyuGjpv5X/8pVEN+MdZqMjfVmiweQSRjOlklXYu65wT9BGEOtCmMs5d3gIvLp4ssfTeuR5OCKOD2kw==