diff --git a/src/peripheral/BaseUniV2Compounder.sol b/src/peripheral/BaseUniV2Compounder.sol new file mode 100644 index 00000000..c6f8bfb4 --- /dev/null +++ b/src/peripheral/BaseUniV2Compounder.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.25 + +pragma solidity ^0.8.25; + +import {UniswapV2TradeLibrary, IUniswapRouterV2} from "./UniswapV2TradeLibrary.sol"; +import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; +import {Math} from "openzeppelin-contracts/utils/math/Math.sol"; + +struct SwapStep { + address[] path; +} + +abstract contract BaseUniV2Compounder { + using SafeERC20 for IERC20; + using Math for uint256; + + IUniswapRouterV2 public uniswapRouter; + + address[] public sellTokens; + SwapStep[] internal sellSwaps; // for each sellToken there are two paths. + + // sell half the rewards for a pool tokenA and half for the tokenB + function sellRewardsForBaseTokensViaUniswapV2() internal { + // caching + IUniswapRouterV2 router = uniswapRouter; + SwapStep memory swap; + + uint256 rewLen = sellTokens.length; + for (uint256 i = 0; i < rewLen;) { + uint256 totAmount = IERC20(sellTokens[i]).balanceOf(address(this)); + + // sell half for tokenA + swap = sellSwaps[2 * i]; + uint256 amount = totAmount.mulDiv(1e18, 2e18, Math.Rounding.Floor); + if (amount > 0) { + UniswapV2TradeLibrary.trade(router, swap.path, address(this), block.timestamp, amount, 0); + } + + // sell the rest for tokenB + swap = sellSwaps[2 * i + 1]; + amount = totAmount - amount; + if (amount > 0) { + UniswapV2TradeLibrary.trade(router, swap.path, address(this), block.timestamp, amount, 0); + } + + unchecked { + ++i; + } + } + } + + // sell all rewards for a single token + function sellRewardsViaUniswapV2() internal { + IUniswapRouterV2 router = uniswapRouter; + SwapStep memory swap; + + uint256 rewLen = sellTokens.length; + for (uint256 i = 0; i < rewLen;) { + uint256 totAmount = IERC20(sellTokens[i]).balanceOf(address(this)); + swap = sellSwaps[i]; + + if (totAmount > 0) { + UniswapV2TradeLibrary.trade(router, swap.path, address(this), block.timestamp, totAmount, 0); + } + + unchecked { + ++i; + } + } + } + + function setUniswapTradeValues( + address newRouter, + address[] memory rewTokens, + SwapStep[] memory newSwaps + ) internal { + // Remove old rewardToken allowance + uint256 sellTokensLen = sellTokens.length; + if (sellTokensLen > 0) { + // caching + address oldRouter = address(uniswapRouter); + address[] memory oldSellTokens = sellTokens; + + // void approvals + for (uint256 i = 0; i < sellTokensLen;) { + IERC20(oldSellTokens[i]).forceApprove(oldRouter, 0); + + unchecked { + ++i; + } + } + } + + // delete old state + delete sellTokens; + delete sellSwaps; + + // Add new allowance + state + address newRewardToken; + sellTokensLen = rewTokens.length; + for (uint256 i = 0; i < sellTokensLen;) { + newRewardToken = rewTokens[i]; + + IERC20(newRewardToken).forceApprove(newRouter, type(uint256).max); + + sellTokens.push(newRewardToken); + + unchecked { + ++i; + } + } + + for (uint256 i=0; i 0 && amountB > 0) { + UniswapV2TradeLibrary.addLiquidity( + uniswapRouter, + depositAssets[0], + depositAssets[1], + amountA, + amountB, + 0, + 0, + to, + deadline + ); + + uint256 amountLP = IERC20(vaultAsset).balanceOf(address(this)); + uint256 minOut = abi.decode(data, (uint256)); + if (amountLP < minOut) revert CompoundFailed(); + } + } + + function setUniswapLpCompounderValues( + address newRouter, + address[2] memory newDepositAssets, + address[] memory rewardTokens, + SwapStep[] memory newSwaps + ) internal { + setUniswapTradeValues(newRouter, rewardTokens, newSwaps); + + address tokenA = newDepositAssets[0]; + address tokenB = newDepositAssets[1]; + + address oldTokenA = depositAssets[0]; + address oldTokenB = depositAssets[1]; + + if (oldTokenA != address(0)) { + IERC20(oldTokenA).forceApprove(address(uniswapRouter), 0); + } + if (oldTokenB != address(0)) { + IERC20(oldTokenB).forceApprove(address(uniswapRouter), 0); + } + + IERC20(tokenA).forceApprove(address(uniswapRouter), type(uint256).max); + IERC20(tokenB).forceApprove(address(uniswapRouter), type(uint256).max); + + depositAssets = newDepositAssets; + } +} \ No newline at end of file diff --git a/src/peripheral/UniswapV2TradeLibrary.sol b/src/peripheral/UniswapV2TradeLibrary.sol new file mode 100644 index 00000000..827ad118 --- /dev/null +++ b/src/peripheral/UniswapV2TradeLibrary.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.25 + +pragma solidity ^0.8.25; + +import {IUniswapRouterV2} from "../interfaces/external/uni/IUniswapRouterV2.sol"; + +library UniswapV2TradeLibrary { + function trade( + IUniswapRouterV2 router, + address[] memory path, + address receiver, + uint256 deadline, + uint256 amount, + uint256 minOut + ) internal returns (uint256[] memory amounts) { + amounts = router.swapExactTokensForTokens(amount, minOut, path, receiver, deadline); + } + + function addLiquidity( + IUniswapRouterV2 router, + address tokenA, + address tokenB, + uint256 amountA, + uint256 amountB, + uint256 amountAMin, + uint256 amountBMin, + address receiver, + uint256 deadline + ) internal returns (uint256 amountOutA, uint256 amountOutB, uint256 lpAmountOut) { + (amountOutA, amountOutB, lpAmountOut) = router.addLiquidity( + tokenA, + tokenB, + amountA, + amountB, + amountAMin, + amountBMin, + receiver, + deadline + ); + } +} \ No newline at end of file diff --git a/src/strategies/peapods/IPeapods.sol b/src/strategies/peapods/IPeapods.sol new file mode 100644 index 00000000..df664bee --- /dev/null +++ b/src/strategies/peapods/IPeapods.sol @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2020 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.25; + +import {IERC20} from "openzeppelin-contracts/interfaces/IERC20.sol"; + +// @dev router contract +interface IIndexUtils { + function addLPAndStake( + address _indexFund, + uint256 _amountIdxTokens, + address _pairedLpTokenProvided, + uint256 _amtPairedLpTokenProvided, + uint256 _amountPairedLpTokenMin, + uint256 _slippage, + uint256 _deadline + ) external; + + function unstakeAndRemoveLP( + address _indexFund, + uint256 _amountStakedTokens, + uint256 _minLPTokens, + uint256 _minPairedLpToken, + uint256 _deadline + ) external; +} + +interface IIndexToken { + function indexTokens(uint256 index) external view returns( + address token, + uint256 weighting, + uint256 basePriceUSDX96, + uint256 c1, + uint256 q1 + ); +} + +interface ICamelotLPToken { + function addLiquidityV2(uint256 _idxLPTokens,uint256 _pairedLPTokens,uint256 _slippage,uint256 _deadline) external; +} + +interface ITokenPod { + // unwraps pod-token into its underlying + // _token: underlying address + // _amount: amount of pod-token to unwrap + // _percentage: 100 + function debond(uint256 _amount,address[] memory _token,uint8[] memory _percentage) external; +} + +interface IStakedToken { + // returns the address of the camelotLP token staked + function stakingToken() external view returns (address lpToken); + + // returns the address of the pool to claim rewards + function poolRewards() external view returns (address poolReward); + + // stakes LP token for rewards. output amount is 1:1 + function stake(address user, uint256 amount) external; + + // unstakes and receives LP token, 1:1 + function unstake(uint256 amount) external; + +} + +interface IPoolRewards { + // returns token address + function rewardsToken() external view returns (address); + + function claimReward(address wallet) external; + + function shares(address who) external view returns (uint256); +} + +interface IPeapods { + function totalSupply() external view returns (uint256); + + function getTotalShares() external view returns (uint256); + + function asset() external view returns (address); + + function initialize(bytes memory adapterInitData, address _wethAddress, bytes memory lidoInitData) external; +} \ No newline at end of file diff --git a/src/strategies/peapods/PeapodsBalancerUniV2Compounder.sol b/src/strategies/peapods/PeapodsBalancerUniV2Compounder.sol new file mode 100644 index 00000000..29d4cf41 --- /dev/null +++ b/src/strategies/peapods/PeapodsBalancerUniV2Compounder.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import {PeapodsDepositor, IERC20, SafeERC20} from "./PeapodsStrategy.sol"; +import {BaseBalancerLpCompounder, HarvestValues, TradePath} from "../../peripheral/BaseBalancerLpCompounder.sol"; +import {BaseUniV2Compounder, SwapStep} from "../../peripheral/BaseUniV2Compounder.sol"; + +/** + * @title ERC4626 Peapods Protocol Vault Adapter + * @author ADN + * @notice ERC4626 wrapper for Peapods protocol + * + * An ERC4626 compliant Wrapper for Peapods. + * Implements harvest func that swaps via Balancer + */ +contract PeapodsDepositorBalancerUniV2Compounder is PeapodsDepositor, BaseBalancerLpCompounder, BaseUniV2Compounder{ + using SafeERC20 for IERC20; + + /*////////////////////////////////////////////////////////////// + INITIALIZATION + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Initialize a new Strategy. + * @param asset_ The underlying asset used for deposit/withdraw and accounting + * @param owner_ Owner of the contract. Controls management functions. + * @param autoDeposit_ Controls if `protocolDeposit` gets called on deposit + * @param strategyInitData_ Encoded data for this specific strategy + */ + function initialize(address asset_, address owner_, bool autoDeposit_, bytes memory strategyInitData_) + external + override + initializer + { + __PeapodsBase_init(asset_, owner_, autoDeposit_, strategyInitData_); + } + + /*////////////////////////////////////////////////////////////// + REWARDS LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @notice The token rewarded from the pendle market + function rewardTokens() external view override returns (address[] memory) { + return _getRewardTokens(); + } + + /*////////////////////////////////////////////////////////////// + HARVESGT LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Claim rewards, swaps to asset and add liquidity + */ + function harvest(bytes memory data) external override onlyKeeperOrOwner { + claim(); + + // caching + address asset_ = asset(); + + // sell for a balancer asset via univ2 + sellRewardsViaUniswapV2(); + + // sell the balancer asset for deposit asset and add liquidity + sellRewardsForLpTokenViaBalancer(asset_, data); + + // compound the lp token + _protocolDeposit(IERC20(asset_).balanceOf(address(this)), 0, data); + + emit Harvested(); + } + + function setHarvestValues( + address newBalancerVault, + TradePath[] memory newTradePaths, + HarvestValues memory harvestValues_, + address newUniswapRouter, + address[] memory rewTokens, + SwapStep[] memory newSwapSteps + ) external onlyOwner { + setUniswapTradeValues(newUniswapRouter, rewTokens, newSwapSteps); + setBalancerLpCompounderValues(newBalancerVault, newTradePaths, harvestValues_); + } + + function _getRewardTokens() internal view returns (address[] memory tokens) { + tokens = new address[](1); + tokens[0] = poolRewards.rewardsToken(); + } +} diff --git a/src/strategies/peapods/PeapodsStrategy.sol b/src/strategies/peapods/PeapodsStrategy.sol new file mode 100644 index 00000000..fec7ed9c --- /dev/null +++ b/src/strategies/peapods/PeapodsStrategy.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import {BaseStrategy, IERC20, IERC20Metadata, SafeERC20, ERC20, Math} from "../BaseStrategy.sol"; +import { + IStakedToken, + IPoolRewards +} from "./IPeapods.sol"; + +/** + * @title ERC4626 Peapods Finance Vault Adapter + * @author ADN + * @notice ERC4626 wrapper for Peapods protocol + * + * Receives Peapods Camelot-LP tokens and stakes them for extra rewards. + * Claim and compound the rewards into more LP tokens + */ +contract PeapodsDepositor is BaseStrategy { + using SafeERC20 for IERC20; + using Math for uint256; + + string internal _name; + string internal _symbol; + + IStakedToken public stakedToken; // vault holding after deposit + IPoolRewards public poolRewards; // pool to get rewards from + + /*////////////////////////////////////////////////////////////// + INITIALIZATION + //////////////////////////////////////////////////////////////*/ + + error InvalidAsset(); + + /** + * @notice Initialize a new Strategy. + * @param asset_ The underlying asset used for deposit/withdraw and accounting + * @param owner_ Owner of the contract. Controls management functions. + * @param autoDeposit_ Controls if `protocolDeposit` gets called on deposit + * @param strategyInitData_ Encoded data for this specific strategy + */ + function initialize(address asset_, address owner_, bool autoDeposit_, bytes memory strategyInitData_) + external + virtual + initializer + { + __PeapodsBase_init(asset_, owner_, autoDeposit_, strategyInitData_); + } + + function __PeapodsBase_init(address asset_, address owner_, bool autoDeposit_, bytes memory strategyInitData_) + internal + onlyInitializing + { + // asset is LP token + __BaseStrategy_init(asset_, owner_, autoDeposit_); + + _name = string.concat("VaultCraft Peapods ", IERC20Metadata(asset_).name(), " Adapter"); + _symbol = string.concat("vcp-", IERC20Metadata(asset_).symbol()); + + // validate staking contract + (address staking_) = abi.decode(strategyInitData_, (address)); + stakedToken = IStakedToken(staking_); + + if(stakedToken.stakingToken() != asset_) + revert InvalidAsset(); + + poolRewards = IPoolRewards(stakedToken.poolRewards()); + + // approve peapods staking contract + IERC20(asset_).approve(staking_, type(uint256).max); + } + + + function name() public view override(IERC20Metadata, ERC20) returns (string memory) { + return _name; + } + + function symbol() public view override(IERC20Metadata, ERC20) returns (string memory) { + return _symbol; + } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + function _totalAssets() internal view override returns (uint256 t) { + // return balance of staked tokens -> 1:1 with LP token + return IERC20(address(stakedToken)).balanceOf(address(this)); + } + + /*////////////////////////////////////////////////////////////// + REWARDS LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @notice Claim liquidity mining rewards given that it's active + function claim() internal override returns (bool success) { + try poolRewards.claimReward(address(this)){ + success = true; + } catch {} + } + + /*////////////////////////////////////////////////////////////// + INTERNAL HOOKS LOGIC + //////////////////////////////////////////////////////////////*/ + + function _protocolDeposit(uint256 amount, uint256, bytes memory) internal override { + // stake lp tokens + stakedToken.stake(address(this), amount); + } + + function _protocolWithdraw(uint256 amount, uint256, bytes memory) internal override { + // unstake lp tokens + stakedToken.unstake(amount); + } +} diff --git a/src/strategies/peapods/PeapodsUniswapV2Compounder.sol b/src/strategies/peapods/PeapodsUniswapV2Compounder.sol new file mode 100644 index 00000000..b4b4ef61 --- /dev/null +++ b/src/strategies/peapods/PeapodsUniswapV2Compounder.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import {PeapodsDepositor, IERC20, SafeERC20} from "./PeapodsStrategy.sol"; +import {BaseUniV2LpCompounder, SwapStep} from "../../peripheral/BaseUniV2LpCompounder.sol"; + +/** + * @title ERC4626 Peapods Protocol Vault Adapter + * @author ADN + * @notice ERC4626 wrapper for Peapods protocol + * + * An ERC4626 compliant Wrapper for Peapods. + * Implements harvest func that swaps via Uniswap V2 + */ +contract PeapodsDepositorUniswapV2Compounder is PeapodsDepositor, BaseUniV2LpCompounder{ + using SafeERC20 for IERC20; + + /*////////////////////////////////////////////////////////////// + INITIALIZATION + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Initialize a new Strategy. + * @param asset_ The underlying asset used for deposit/withdraw and accounting + * @param owner_ Owner of the contract. Controls management functions. + * @param autoDeposit_ Controls if `protocolDeposit` gets called on deposit + * @param strategyInitData_ Encoded data for this specific strategy + */ + function initialize(address asset_, address owner_, bool autoDeposit_, bytes memory strategyInitData_) + external + override + initializer + { + __PeapodsBase_init(asset_, owner_, autoDeposit_, strategyInitData_); + } + + /*////////////////////////////////////////////////////////////// + REWARDS LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @notice The token rewarded from the pendle market + function rewardTokens() external view override returns (address[] memory) { + return _getRewardTokens(); + } + + /*////////////////////////////////////////////////////////////// + HARVESGT LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Claim rewards, swaps to asset and add liquidity + */ + function harvest(bytes memory data) external override onlyKeeperOrOwner { + claim(); + + // caching + address asset_ = asset(); + + sellRewardsForLpTokenViaUniswap(asset_, address(this), block.timestamp, data); + + _protocolDeposit(IERC20(asset_).balanceOf(address(this)), 0, data); + + emit Harvested(); + } + + function setHarvestValues( + address newRouter, + address[2] memory newDepositAssets, + SwapStep[] memory newSwaps + ) external onlyOwner { + setUniswapLpCompounderValues(newRouter, newDepositAssets, _getRewardTokens(), newSwaps); + } + + // allow owner to withdraw eventual dust amount of tokens + // from the compounding operation + function withdrawDust(address token) external onlyOwner { + if(token != depositAssets[0] && token != depositAssets[1]) + revert("Invalid Token"); + + IERC20(token).safeTransfer(owner, IERC20(token).balanceOf(address(this))); + } + + function _getRewardTokens() internal view returns (address[] memory tokens) { + tokens = new address[](1); + tokens[0] = poolRewards.rewardsToken(); + } +} diff --git a/test/strategies/peapods/PeapodsBalancerUniCompounder.json b/test/strategies/peapods/PeapodsBalancerUniCompounder.json new file mode 100644 index 00000000..002fc4d2 --- /dev/null +++ b/test/strategies/peapods/PeapodsBalancerUniCompounder.json @@ -0,0 +1,83 @@ +{ + "length": 1, + "configs": [ + { + "base": { + "asset": "0x473aA22927ACcAD56303019f75C1b3C735f79fd5", + "blockNumber": 19092311, + "defaultAmount": 1000000000000000000, + "delta": 10000000000000000, + "maxDeposit": 1000000000000000000000, + "maxWithdraw": 1000000000000000000000, + "minDeposit": 1000000000000000, + "minWithdraw": 1000000000000000, + "network": "mainnet", + "testId": "Peapods Uniswap Compounder - pDAI" + }, + "specific": { + "init": { + "stakingContract": "0x4D57ad8FB14311e1Fc4b3fcaC62129506FF373b1" + }, + "harvest": { + "uniswap":{ + "uniswapRouter": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + "rewTokens": [ + "0x02f92800F57BCD74066F5709F1Daa1A4302Df875" + ], + "tradePaths": [ + { + "length": "5", + "path": [ + "0x02f92800F57BCD74066F5709F1Daa1A4302Df875", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + } + ] + }, + "balancer": { + "balancerVault": "0xBA12222222228d8Ba445958a75a0704d566BF2C8", + "harvestValues": { + "amountsInLen": 2, + "depositAsset": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "indexIn": 1, + "indexInUserData": 1, + "poolId": "0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e", + "underlyings": [ + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + "tradePaths": { + "length": 1, + "structs": [ + { + "assets": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" + ], + "limits": [ + 57896044618658097711785492504343953926634992332820282019728792003956564819967, + 57896044618658097711785492504343953926634992332820282019728792003956564819967 + ], + "swaps": [ + { + "a-poolId": "0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e", + "b-assetInIndex": 0, + "c-assetOutIndex": 1, + "d-amount": 0, + "e-userData": "" + } + ] + } + ] + } + } + } + } + } + ] + } + \ No newline at end of file diff --git a/test/strategies/peapods/PeapodsBalancerUniCompounder.t.sol b/test/strategies/peapods/PeapodsBalancerUniCompounder.t.sol new file mode 100644 index 00000000..465ab08c --- /dev/null +++ b/test/strategies/peapods/PeapodsBalancerUniCompounder.t.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import {Test} from "forge-std/Test.sol"; + +import {PeapodsDepositorBalancerUniV2Compounder, SwapStep} from "../../../src/strategies/peapods/PeapodsBalancerUniV2Compounder.sol"; +import { + BalancerCompounder, + IERC20, + HarvestValues, + TradePath +} from "../../../src/strategies/balancer/BalancerCompounder.sol"; +import {IAsset, BatchSwapStep} from "../../../src/peripheral/BalancerTradeLibrary.sol"; +import {IStakedToken} from "../../../src/strategies/peapods/PeapodsStrategy.sol"; +import {BaseStrategyTest, IBaseStrategy, TestConfig, stdJson, Math} from "../BaseStrategyTest.sol"; +import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +contract PeapodsUniswapV2CompounderTest is BaseStrategyTest { + using stdJson for string; + using Math for uint256; + + address asset; + address stakingContract; + + function setUp() public { + _setUpBaseTest(0, "./test/strategies/peapods/PeapodsBalancerUniCompounder.json"); + } + + function _setUpStrategy(string memory json_, string memory index_, TestConfig memory testConfig_) + internal + override + returns (IBaseStrategy) + { + // Read strategy init values + stakingContract = json_.readAddress(string.concat(".configs[", index_, "].specific.init.stakingContract")); + + // Deploy Strategy + PeapodsDepositorBalancerUniV2Compounder strategy = new PeapodsDepositorBalancerUniV2Compounder(); + + strategy.initialize( + testConfig_.asset, + address(this), + true, + abi.encode(stakingContract) + ); + + // Set Harvest values - uniswap + address uniswapRouter = json_.readAddress(string.concat(".configs[", index_, "].specific.harvest.uniswap.uniswapRouter")); + + // assets to buy with rewards and to add to liquidity + address[] memory rewToken = new address[](1); + rewToken[0] = json_.readAddress( + string.concat(".configs[", index_, "].specific.harvest.uniswap.rewTokens[0]") + ); + + // set Uniswap trade paths + SwapStep[] memory swaps = new SwapStep[](1); + + uint256 lenSwap0 = json_.readUint( + string.concat(".configs[", index_, "].specific.harvest.uniswap.tradePaths[0].length") + ); + address[] memory swap0 = new address[](lenSwap0); // PEAS - WETH + for(uint256 i=0; i