"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SlippageTest = void 0;
const tslib_1 = require("tslib");
// Uniswap V3 Contracts
const color_1 = (0, tslib_1.__importDefault)(require("@oclif/color"));
const UniswapV3Factory_json_1 = (0, tslib_1.__importDefault)(require("@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json"));
const UniswapV3Pool_json_1 = (0, tslib_1.__importDefault)(require("@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json"));
const QuoterV2_json_1 = (0, tslib_1.__importDefault)(require("@uniswap/v3-periphery/artifacts/contracts/lens/QuoterV2.sol/QuoterV2.json"));
const NFTDescriptor_json_1 = (0, tslib_1.__importDefault)(require("@uniswap/v3-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json"));
const NonfungiblePositionManager_json_1 = (0, tslib_1.__importDefault)(require("@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"));
const NonfungibleTokenPositionDescriptor_json_1 = (0, tslib_1.__importDefault)(require("@uniswap/v3-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json"));
const SwapRouter_json_1 = (0, tslib_1.__importDefault)(require("@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json"));
const cli_table3_1 = (0, tslib_1.__importDefault)(require("cli-table3"));
const cli_ux_1 = (0, tslib_1.__importDefault)(require("cli-ux"));
const ethers_1 = require("ethers");
const utils_1 = require("ethers/lib/utils");
const UniLiquidityManager_json_1 = require("../artifacts/contracts/UniLiquidityManager.sol/UniLiquidityManager.json");
// eslint-disable-next-line camelcase
const uniswap_position_quoter_1 = require("@steerprotocol/uniswap-position-quoter");
const network_1 = require("../utils/network");
const constants_1 = require("./../utils/constants");
const replay_1 = require("./replay");
const ERC20_json_1 = require("../artifacts/contracts/freeToken.sol/ERC20.json");
const inquirer_1 = (0, tslib_1.__importDefault)(require("inquirer"));
const kill = require('kill-port');
const POOR_ADDRESS = '0x0000000000000000133700000000000000000000';
const RICH_ADDRESS = '0x0000000000000000133800000000000000000000';
// Uniswap v3 artifacts
const artifacts = {
    UniswapV3Factory: UniswapV3Factory_json_1.default,
    SwapRouter: SwapRouter_json_1.default,
    NFTDescriptor: NFTDescriptor_json_1.default,
    NonfungibleTokenPositionDescriptor: NonfungibleTokenPositionDescriptor_json_1.default,
    NonfungiblePositionManager: NonfungiblePositionManager_json_1.default,
    UniswapV3Pool: UniswapV3Pool_json_1.default,
};
class SlippageTest {
    // eslint-disable-next-line complexity
    //   async run(config: Config, strategy: Strategy, stats: Stats, candles: Array<Candle>): Promise<void> {
    // eslint-disable-next-line complexity
    async run(poolAddress) {
        var _a;
        try {
            // Setup a local RPC Provider for ethers
            const provider = new ethers_1.ethers.providers.JsonRpcProvider('http://localhost:8545');
            // Uniswap Contract Addresses
            const swapRouterAddress = '0xE592427A0AEce92De3Edee1F18E0157C05861564';
            const NFTManagerAddress = '0xC36442b4a4522E871399CD717aBDD847Ab11FE88';
            const UniswapV3Factory = '0x1F98431c8aD98523631AE4a59f267346ea31F984';
            const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
            // const quoterAddress = '0x61fFE014bA17989E743c5F6cB21bF9697530B21e' // V3 '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'
            // Read in pool info
            // Setup swap router contract
            const swapRouterContract = new ethers_1.Contract(swapRouterAddress, new utils_1.Interface(artifacts.SwapRouter.abi)).connect(provider.getSigner(0));
            // Setup pool contract
            const poolContract = new ethers_1.Contract(poolAddress, new utils_1.Interface(artifacts.UniswapV3Pool.abi)).connect(provider.getSigner(0));
            //   // Parse historical data
            //   const historicalData: HistoricalData = JSON.parse(
            //     await fs.readFile(config.location, 'utf-8'),
            //   )
            // Copy the historical data, so we can mutate the data
            //   let txEventsArray = [...historicalData]
            // Sort the events by timestamp so we replay them in proper order
            //   txEventsArray = txEventsArray.sort(function (a, b) {
            //     return Number(a.timestamp) - Number(b.timestamp)
            //   })
            const token0Address = await poolContract.token0();
            const token1Address = await poolContract.token1();
            // Pull the pool fee for the current pool we are working with
            // This is using for mint, burn, and setup of the associated vault contract
            const poolFee = await poolContract.fee();
            //   const alchemy = new Alchemy(settings);
            // Get the latest block
            // const latestBlock = alchemy.core.getBlockNumber();
            // const blockInfo: any = await axios.get('https://api.blockcypher.com/v1/eth/main')
            // const latestBlock = blockInfo.data.height
            // console.log(latestBlock)
            // copy info to memory before wiping the tokens
            // get decimals, symbols, and pool balances
            const _token0 = new ethers_1.Contract(token0Address, constants_1.ERC20_ABI, provider);
            const _token1 = new ethers_1.Contract(token1Address, constants_1.ERC20_ABI, provider);
            let token0Balance = await _token0.balanceOf(poolAddress);
            let token1Balance = await _token1.balanceOf(poolAddress);
            // console.log(token0Balance.toString())
            // console.log(token1Balance.toString())
            const token0Decimals = await _token0.decimals();
            const token1Decimals = await _token1.decimals();
            let token0Symbol;
            let token1Symbol;
            try {
                token0Symbol = await _token0.symbol();
                token1Symbol = await _token1.symbol();
            }
            catch (error) {
                console.log('Sorry, we currently do not support DSToken interfaces. Try again with different assets.');
                process.exit(1);
            }
            const t0balAdj = (Number(token0Balance.toString()) / (10 ** token0Decimals)).toLocaleString('fullwide', { useGrouping: false });
            console.log('Current pool holdings of ' + token0Symbol + ': ' + t0balAdj);
            const vaultQuestions = [
                {
                    type: 'input',
                    name: 'token0InitialAmount',
                    message: `Amount of ${token0Symbol} to market make with (token1 amount will mirror this value):`,
                    validate: (value) => {
                        try {
                            (0, utils_1.parseUnits)(String(value), token0Decimals);
                            return true;
                        }
                        catch {
                            return 'Please enter a valid number. Example: 1.23';
                        }
                    },
                },
                // {
                //   type: 'input',
                //   name: 'token1InitialAmount',
                //   message: `Starting amount of ${
                //     token1Symbol
                //   } in the vault:`,
                //   validate: (value: string) => {
                //     try {
                //       parseUnits(
                //         String(value),
                //         token1Decimals,
                //       )
                //       return true
                //     } catch {
                //       return 'Please enter a valid number. Example: 1.23'
                //     }
                //   },
                // },
            ];
            // console.log(
            //   '⚠️ Significant token amounts relevant to the pool will negatively effect the pool simulation',
            // )
            // We parse the user responses
            const { token0InitialAmount } = await inquirer_1.default.prompt(vaultQuestions);
            console.log('Acquiring assets for simulation...');
            const { mainActor, mainSigner, token0, token1 } = (await provideTokensToAccounts([token0Address, token1Address], poolAddress, token0Balance, token1Balance, provider));
            // Process approvals for the swap router and the nft manager
            // (which mints and burns liquidity)
            await token0
                .connect(mainSigner)
                .approve(swapRouterAddress, (0, utils_1.parseUnits)('10', 42));
            await token1
                .connect(mainSigner)
                .approve(swapRouterAddress, (0, utils_1.parseUnits)('10', 42));
            // Create contract factory for the UniLiquidityManger
            // eslint-disable-next-line camelcase
            const ManagerFactory = new ethers_1.ethers.ContractFactory(UniLiquidityManager_json_1.abi, UniLiquidityManager_json_1.bytecode);
            const QuoterFactory = new ethers_1.ethers.ContractFactory(QuoterV2_json_1.default.abi, QuoterV2_json_1.default.bytecode);
            const DeployedQuoter = await QuoterFactory.connect(mainSigner).deploy(UniswapV3Factory, WETH);
            const DeployedQuoterAddress = DeployedQuoter.address;
            const quoterContract = new ethers_1.Contract(DeployedQuoterAddress, new utils_1.Interface(QuoterV2_json_1.default.abi)).connect(provider.getSigner(0));
            const PositionQuoter = new uniswap_position_quoter_1.CustomRouter(quoterContract);
            // Deploy a new instance of the UniLiquidityManager
            const DeployedManager = await ManagerFactory.connect(mainSigner).deploy();
            // Next we need to encode the input params of the vault, this is abi encoded as different
            // vault will have different params
            const initParams = encodeInitUniV3VaultParams(token0Address, token1Address, poolFee, ethers_1.BigNumber.from(10).pow(36), 1000, 45);
            // Now that we have encoded the vault params, we can initialize the vault
            const txV3 = await DeployedManager.connect(mainSigner).initialize(mainActor.address, // Orchestrator
            mainActor.address, // Steer Address
            mainActor.address, // Governance
            0, initParams);
            // Wait for the tx to be mined
            await txV3.wait();
            const fee = (await poolContract.fee()).toLocaleString('fullwide', { useGrouping: false });
            const txSetPool = await DeployedManager.setPool(UniswapV3Factory, fee);
            await txSetPool.wait();
            // SIMULATE
            // let token0Balance = await token0.balanceOf(poolAddress);
            // let token1Balance = await token1.balanceOf(poolAddress);
            // const token0Decimals = await token0.decimals();
            // const token1Decimals = await token1.decimals();
            // const token0Symbol = await token0.symbol();
            // const token1Symbol = await token1.symbol();
            let currentSlot = await poolContract.slot0();
            console.log('Simulating current slippage...');
            const startingTick = currentSlot.tick;
            // ratio * 1e18
            const X192 = ethers_1.BigNumber.from(2).pow(192);
            let initPrice = (currentSlot.sqrtPriceX96.pow(2).mul((0, utils_1.parseEther)('1'))).div(X192); // times 1e18
            let priceRatio = Number(initPrice.toString()) / (1e18);
            const preSlippageResults0for1 = [];
            const preSlippageResults1for0 = [];
            const size0for1 = [];
            const size1for0 = [];
            // Percentages of pools tokens to try swapping
            const sizes = [0.00001, 0.0001, 0.001, 0.01, 0.05, 0.10, 0.33, 0.75];
            const sizeMultiplier = 1000000;
            for (let i = 0; i < sizes.length; i++) {
                const payload0 = ((ethers_1.BigNumber.from((sizes[i] * sizeMultiplier).toString()).mul(token0Balance)).div(sizeMultiplier));
                try {
                    const amountOutFrom0 = await swapRouterContract.connect(mainSigner).callStatic.exactInputSingle({
                        tokenIn: token0Address,
                        tokenOut: token1Address,
                        fee: poolFee,
                        recipient: RICH_ADDRESS,
                        deadline: 2676518139,
                        amountIn: payload0,
                        amountOutMinimum: 0,
                        sqrtPriceLimitX96: 0
                    });
                    // console.log(amountOutFrom0.toString())
                    // calc ideal amount for size
                    const nonSlipOut = Number(payload0.mul(initPrice).toString()) / 1e18;
                    // console.log(nonSlipOut)
                    const slippage = (((nonSlipOut - Number(amountOutFrom0.toString())) / nonSlipOut) * 100) - (poolFee / 1e4);
                    // const slip = ((nonSlipOut / Number(amountOutFrom0.toString())) - 1 ) * 100
                    console.log(slippage.toPrecision(4) + '% slippage for ' + token0Symbol + ' into pool at size of ' +
                        (Number(payload0.toString()) / 10 ** token0Decimals).toString() + ' or ' + (sizes[i] * 100).toString() + '% of the pool\'s holdings of ' + token0Symbol);
                    preSlippageResults0for1.push(slippage);
                    size0for1.push((Number(payload0.toString()) / 10 ** token0Decimals).toString());
                }
                catch (error) {
                    console.log('tx failed');
                }
            }
            console.log('--------------');
            // Token1
            // invert init price
            let invertedInit = ethers_1.BigNumber.from('0x' + Math.round(((priceRatio ** -1) * 1e18)).toString(16));
            for (let i = 0; i < sizes.length; i++) {
                const payload1 = ((ethers_1.BigNumber.from((sizes[i] * sizeMultiplier).toString()).mul(token1Balance)).div(sizeMultiplier));
                try {
                    const amountOutFrom1 = await swapRouterContract.connect(mainSigner).callStatic.exactInputSingle({
                        tokenIn: token1Address,
                        tokenOut: token0Address,
                        fee: poolFee,
                        recipient: RICH_ADDRESS,
                        deadline: 2676518139,
                        amountIn: payload1,
                        amountOutMinimum: 0,
                        sqrtPriceLimitX96: 0
                    });
                    // console.log(amountOutFrom0.toString())
                    // calc ideal amount for size
                    const nonSlipOut = Number(payload1.mul(invertedInit).toString()) / 1e18;
                    // console.log(nonSlipOut)
                    const slippage = (((nonSlipOut - Number(amountOutFrom1.toString())) / nonSlipOut) * 100) - (poolFee / 1e4);
                    // const slip = ((nonSlipOut / Number(amountOutFrom0.toString())) - 1 ) * 100
                    console.log(slippage.toPrecision(4) + '% slippage for ' + token1Symbol + ' into pool at size of ' + (Number(payload1.toString()) / 10 ** token1Decimals).toString()
                        + ' or ' + (sizes[i] * 100).toString() + '% of the pool\'s holdings of ' + token1Symbol);
                    preSlippageResults1for0.push(slippage);
                    size1for0.push((Number(payload1.toString()) / 10 ** token1Decimals).toString());
                }
                catch (error) {
                    console.log('tx failed');
                }
            }
            console.log('\nTesting Strategy...');
            // Now that we have a clean wallet to work from we need
            // to setup some tokens so it can begin trading. First,
            // we provide it ETH
            await provider.send('hardhat_setBalance', [
                RICH_ADDRESS,
                (0, utils_1.parseEther)('1000').toHexString(),
            ]);
            // Next, we should impersonate the poor actor and approve for the
            // uni liq vault
            const [, poorSigner] = await getImpersonationSigner(POOR_ADDRESS, provider);
            // Then we convert the initial amounts the user provided into their native values
            const t0initialNative = (0, utils_1.parseUnits)(token0InitialAmount.toString(), token0Decimals);
            // convert this amount to t1 so holding ratio is 50:50
            const t1initialNative = t0initialNative.mul(initPrice).div((0, utils_1.parseEther)('1'));
            // const t1initialNative = parseUnits(token1InitialAmount.toString(), token1Decimals)
            // Approve tokens
            await token0
                .connect(mainSigner)
                .approve(DeployedManager.address, (0, utils_1.parseEther)('999999999999').toHexString());
            await token1
                .connect(mainSigner)
                .approve(DeployedManager.address, (0, utils_1.parseEther)('999999999999').toHexString());
            // Next we want to bootstrap the vault by supplying tokens from the
            // poor actor to the uni liq vault
            const depTx = await DeployedManager.connect(mainSigner).deposit(t0initialNative, t1initialNative, 0, 0, RICH_ADDRESS);
            // Wait for the tx to be mined
            await depTx.wait();
            const poolObj = new ethers_1.Contract(poolAddress, UniswapV3Pool_json_1.default.abi, mainSigner);
            let output;
            const newPositions = getNormalCurvePositions(startingTick, poolFee);
            console.log('newPositions', newPositions);
            let swapResult = { amountToSwap: ethers_1.BigNumber.from('0'), zeroForOne: false };
            if (!swapResult) {
                console.log('🚨 🚨 Swap result failure');
                throw new Error("Swap result failure");
            }
            // Slippage check ~ can be configured, but not necessarily a concern with assumptions
            const sqrtPrice = swapResult.zeroForOne ? ethers_1.BigNumber.from('50').mul(currentSlot.sqrtPriceX96).div('100') : ethers_1.BigNumber.from('150').mul(currentSlot.sqrtPriceX96).div('100');
            // convert pos into LiquidityPositionsStruct
            const lowerTick = [];
            const upperTick = [];
            const relativeWeight = [];
            for (const isoPos of newPositions) {
                lowerTick.push(Number(isoPos.lowerTick));
                upperTick.push(Number(isoPos.upperTick));
                relativeWeight.push(Number(isoPos.weight));
            }
            const LiqPos = {
                lowerTick: lowerTick,
                upperTick: upperTick,
                relativeWeight: relativeWeight,
            };
            await (0, network_1.setNextBlockTimestamp)(120, provider);
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            await (0, replay_1.mineBlock)(provider);
            // const totalWeight: BigNumberish = output.valuesArray[0]
            const timeSensitive = ethers_1.utils.defaultAbiCoder.encode(['int256', 'uint160'], [(_a = swapResult === null || swapResult === void 0 ? void 0 : swapResult.amountToSwap) === null || _a === void 0 ? void 0 : _a.mul((swapResult === null || swapResult === void 0 ? void 0 : swapResult.zeroForOne) ? 1 : -1), sqrtPrice]);
            // Pre swap this so we end up at the original tick
            // await swapRouterContract.connect(mainSigner).exactInputSingle(
            //   {
            //       tokenIn: swapResult?.zeroForOne ? token0Address : token1Address,
            //       tokenOut:swapResult?.zeroForOne ? token1Address : token0Address,
            //       fee:poolFee,
            //       recipient:RICH_ADDRESS,
            //       deadline:2676518139,
            //       amountIn:swapResult?.amountToSwap,
            //       amountOutMinimum:0,
            //       sqrtPriceLimitX96:0
            //   })
            // make sure we got into the right spot
            try {
                const tendTx = await DeployedManager.connect(mainSigner).tend(10000, LiqPos, timeSensitive, {
                    gasLimit: 30000000,
                });
                let _tendTx = await tendTx.wait();
            }
            catch (error) {
                console.log('🚨 Tend Failure! Make sure your strategy is making positions on initializable ticks (Relative to pool fee)');
                throw error;
            }
            // Rerun simulation
            // console.log('Positions set, testing slippage again')
            // @todo maybe uncomment these two lines, this is tricky depending on the current liquidity dependency and how much we can remove to aggregate @note
            token0Balance = await token0.balanceOf(poolAddress);
            token1Balance = await token1.balanceOf(poolAddress);
            // console.log(token0Balance.toString())
            // console.log(token1Balance.toString())
            currentSlot = await poolContract.slot0();
            const postTendTick = currentSlot.tick;
            const tickChange = startingTick - postTendTick;
            console.log('Post LP Slippage...');
            initPrice = (currentSlot.sqrtPriceX96.pow(2).mul((0, utils_1.parseEther)('1'))).div(X192); // times 1e18
            priceRatio = Number(initPrice.toString()) / (1e18);
            const postSlippageResults0for1 = [];
            const postSlippageResults1for0 = [];
            for (let i = 0; i < sizes.length; i++) {
                const payload0 = ((ethers_1.BigNumber.from((sizes[i] * sizeMultiplier).toString()).mul(token0Balance)).div(sizeMultiplier));
                try {
                    const amountOutFrom0 = await swapRouterContract.connect(mainSigner).callStatic.exactInputSingle({
                        tokenIn: token0Address,
                        tokenOut: token1Address,
                        fee: poolFee,
                        recipient: RICH_ADDRESS,
                        deadline: 2676518139,
                        amountIn: payload0,
                        amountOutMinimum: 0,
                        sqrtPriceLimitX96: 0
                    });
                    // console.log(amountOutFrom0.toString())
                    // calc ideal amount for size
                    const nonSlipOut = Number(payload0.mul(initPrice).toString()) / 1e18;
                    // console.log(nonSlipOut)
                    const slippage = (((nonSlipOut - Number(amountOutFrom0.toString())) / nonSlipOut) * 100) - (poolFee / 1e4);
                    // const slip = ((nonSlipOut / Number(amountOutFrom0.toString())) - 1 ) * 100
                    console.log(slippage.toPrecision(4) + '% slippage for ' + token0Symbol + ' into pool at size of ' +
                        (Number(payload0.toString()) / 10 ** token0Decimals).toString() + ' or ' + (sizes[i] * 100).toString() + '% of the pool\'s holdings of ' + token0Symbol);
                    postSlippageResults0for1.push(slippage);
                }
                catch (error) {
                    console.log('tx failed');
                }
            }
            console.log('--------------');
            // Token1
            // invert init price
            invertedInit = ethers_1.BigNumber.from('0x' + Math.round(((priceRatio ** -1) * 1e18)).toString(16));
            for (let i = 0; i < sizes.length; i++) {
                const payload1 = ((ethers_1.BigNumber.from((sizes[i] * sizeMultiplier).toString()).mul(token1Balance)).div(sizeMultiplier));
                try {
                    const amountOutFrom1 = await swapRouterContract.connect(mainSigner).callStatic.exactInputSingle({
                        tokenIn: token1Address,
                        tokenOut: token0Address,
                        fee: poolFee,
                        recipient: RICH_ADDRESS,
                        deadline: 2676518139,
                        amountIn: payload1,
                        amountOutMinimum: 0,
                        sqrtPriceLimitX96: 0
                    });
                    // console.log(amountOutFrom0.toString())
                    // calc ideal amount for size
                    const nonSlipOut = Number(payload1.mul(invertedInit).toString()) / 1e18;
                    // console.log(nonSlipOut)
                    const slippage = (((nonSlipOut - Number(amountOutFrom1.toString())) / nonSlipOut) * 100) - (poolFee / 1e4);
                    // const slip = ((nonSlipOut / Number(amountOutFrom0.toString())) - 1 ) * 100
                    console.log(slippage.toPrecision(4) + '% slippage for ' + token1Symbol + ' into pool at size of ' + (Number(payload1.toString()) / 10 ** token1Decimals).toString()
                        + ' or ' + (sizes[i] * 100).toString() + '% of the pool\'s holdings of ' + token1Symbol);
                    postSlippageResults1for0.push(slippage);
                }
                catch (error) {
                    console.log('tx failed');
                }
            }
            console.log(color_1.default.green('\nComparison Info\n'));
            console.log(color_1.default.yellow('Swapping ' + token0Symbol + ' for ' + token1Symbol));
            const _table = new cli_table3_1.default({
                head: ['% of Pool Size Swap', 'Size of ' + token0Symbol, 'Slippage Improvement with Liquidity'],
            });
            for (let index = 0; index < sizes.length; index++) {
                _table.push([(sizes[index] * 100).toString() + '% of pool\'s ' + token0Symbol, size0for1[index], (preSlippageResults0for1[index] - postSlippageResults0for1[index]).toFixed(4) + '%']);
            }
            console.log(_table.toString());
            console.log(color_1.default.blue('\nSwapping ' + token1Symbol + ' for ' + token0Symbol));
            const _table1 = new cli_table3_1.default({
                head: ['% of Pool Size Swap', 'Size of ' + token1Symbol, 'Slippage Improvement with Liquidity'],
            });
            for (let index = 0; index < sizes.length; index++) {
                _table1.push([(sizes[index] * 100).toString() + '% of pool\'s ' + token1Symbol, size1for0[index], (preSlippageResults1for0[index] - postSlippageResults1for0[index]).toFixed(4) + '%']);
            }
            console.log(_table1.toString());
            // console.log(color.magenta(('Keep in mind balancing moved the original tick from ' + startingTick + ' to ' + postTendTick + ', or a price impact of ' + (tickChange/100) + '%\nThis can bias results to one direction of swaps')))
            // console.log('END')
            process.exit(0);
        }
        catch (error) {
            //   progressBar.stop()
            await kill(8545);
            setTimeout(() => {
                throw error;
            }, 10000);
        }
        // @note
        // ----END TEST----
    }
    getTopHolder(holders, tokenAddress, type, provider) {
        return holders.find(async (holder) => {
            const swapInToken = new ethers_1.Contract(tokenAddress, constants_1.ERC20_ABI, provider);
            const balance = await swapInToken.balanceOf(holder.address);
            return holder.address_type === type && balance.gt(0);
        });
    }
    displayTargetEntityDetails(poolDetails) {
        cli_ux_1.default.table([poolDetails], {
            id: {
                header: 'Address',
                get: (row) => row.id,
            },
            pair: {
                header: 'Pair',
                get: (row) => `${row.token0.name}/${row.token1.name}`,
            },
            feeTier: {
                header: 'Feed Tier',
                get: (row) => `${Number(row.feeTier) / 10000}%`,
            },
            createdAtTimestamp: {
                header: 'Created At',
                get: (row) => new Date(Number(row.createdAtTimestamp) * 1000).toDateString(),
            },
            Liquidity: {
                header: 'Liquidity',
                get: (row) => Number(row.totalValueLockedUSD).toFixed(2),
            },
        });
    }
}
exports.SlippageTest = SlippageTest;
const getImpersonationSigner = async (address, provider) => {
    const actor = new network_1.Impersonator(address);
    const signer = await actor.impersonate(provider);
    return [actor, signer];
};
function getNormalCurvePositions(currentTick, poolFee) {
    let tickSpacing = 600;
    switch (poolFee) {
        case 10000:
            tickSpacing = 200;
            break;
        case 3000:
            tickSpacing = 60;
            break;
        case 500:
            tickSpacing = 10;
            break;
        case 100:
            tickSpacing = 1;
            break;
        default:
            break;
    }
    const std = 600;
    // Find nearest initializable tick
    const centerTick = Math.round(currentTick / tickSpacing) * tickSpacing;
    const newPositions = [];
    newPositions.push({
        lowerTick: centerTick - (3 * std),
        upperTick: centerTick - (2 * std),
        weight: 15,
    });
    newPositions.push({
        lowerTick: centerTick - (2 * std),
        upperTick: centerTick - (1 * std),
        weight: 136,
    });
    newPositions.push({
        lowerTick: centerTick - (std),
        upperTick: centerTick + (std),
        weight: 682,
    });
    newPositions.push({
        lowerTick: centerTick + (1 * std),
        upperTick: centerTick + (2 * std),
        weight: 136,
    });
    newPositions.push({
        lowerTick: centerTick + (2 * std),
        upperTick: centerTick + (3 * std),
        weight: 15,
    });
    return newPositions;
}
const encodeInitUniV3VaultParams = (token0, token1, fee, maxTotalSupply, _maxTickChange, _twapInterval) => {
    // Handle optional params
    if (maxTotalSupply === undefined) {
        maxTotalSupply = ethers_1.BigNumber.from(10).pow(36);
    }
    if (typeof _maxTickChange === 'undefined' || _maxTickChange === null) {
        _maxTickChange = 250;
    }
    if (typeof _twapInterval === 'undefined') {
        _twapInterval = 45;
    }
    return ethers_1.utils.defaultAbiCoder.encode([
        'address',
        'address',
        'uint24',
        'uint256',
        'int24',
        'int32',
    ], [
        token0,
        token1,
        fee,
        maxTotalSupply,
        _maxTickChange,
        _twapInterval, // How much time
    ]);
};
// The goal is to find the largest holders and compile all of their assets into a single address
// this will allow us to test the vault with a single address that has a large amount of assets
// and replay the transactions on mainnet to see how the vault performs
const provideTokensToAccounts = async (tokens, poolAddress, pool0Bal, pool1Bal, provider) => {
    // replace bytecode with free mint code
    await provider.send("hardhat_setCode", [
        tokens[0],
        ERC20_json_1.deployedBytecode,
    ]);
    // replace bytecode with free mint code
    await provider.send("hardhat_setCode", [
        tokens[1],
        ERC20_json_1.deployedBytecode,
    ]);
    // Setup an impersonation account for the first holder
    // in the service provider response
    const [mainActor, mainSigner] = await getImpersonationSigner(RICH_ADDRESS, provider);
    await provider.send('hardhat_setBalance', [
        mainActor.address,
        (0, utils_1.parseEther)('1000').toHexString(),
    ]);
    const freedToken0 = new ethers_1.Contract(tokens[0], ERC20_json_1.abi, provider);
    const freedToken1 = new ethers_1.Contract(tokens[1], ERC20_json_1.abi, provider);
    // console.log(await freedToken0.balanceOf(RICH_ADDRESS))
    await freedToken0.connect(mainSigner).mint(RICH_ADDRESS, (0, utils_1.parseUnits)('10', 36));
    await freedToken1.connect(mainSigner).mint(RICH_ADDRESS, (0, utils_1.parseUnits)('10', 36));
    // Check balance of pool address and see if the storage is valid, if so dont mint
    const _poolBal0 = await freedToken0.balanceOf(poolAddress);
    const _poolBal1 = await freedToken1.balanceOf(poolAddress);
    if (_poolBal0.eq(0))
        await freedToken0.connect(mainSigner).mint(poolAddress, pool0Bal);
    if (_poolBal1.eq(0))
        await freedToken1.connect(mainSigner).mint(poolAddress, pool1Bal);
    // console.log(await freedToken0.balanceOf(RICH_ADDRESS))
    // Once we have the op holders, we can setup the tokens
    // and connect their signers for token0 and token1
    const token0 = new ethers_1.Contract(tokens[0], constants_1.ERC20_ABI, mainSigner);
    const token1 = new ethers_1.Contract(tokens[1], constants_1.ERC20_ABI, mainSigner);
    return { mainActor, mainSigner, token0, token1 };
};
