From 2b50b26f3e624b313ae0c95086d5c88dc7a26162 Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Thu, 16 Sep 2021 12:47:37 +0530 Subject: [PATCH 1/9] feature: Periphery.sol for interacting with Vault.sol --- contracts/Periphery.sol | 73 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 contracts/Periphery.sol diff --git a/contracts/Periphery.sol b/contracts/Periphery.sol new file mode 100644 index 0000000..d19ea14 --- /dev/null +++ b/contracts/Periphery.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.7.5; +pragma abicoder v2; + +import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol'; +import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol'; +import '@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol'; +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; + +import './interfaces/IVault.sol'; +import "./interfaces/IERC20Metadata.sol"; + +contract Periphery { + using SafeERC20 for IERC20Metadata; + + ISwapRouter public immutable swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); + IQuoter public immutable quoter = IQuoter(0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6); + + /// @notice Calls IVault's deposit method and sends all money back to user after transactions + /// @param amountIn Value of token0 to be deposited + function vaultDeposit(uint256 amountIn) external minimumAmount(amountIn) { + IVault vault = IVault(0x0); + uint24 poolFee = vault.pool().fee(); + IERC20Metadata token0 = vault.token0(); + IERC20Metadata token1 = vault.token1(); + + // transfer token0 from sender to contract & approve router to spend it + token0.safeTransferFrom(msg.sender, address(this), amountIn); + token0.approve(address(swapRouter), amountIn); + + // swap token0 for token1 + ISwapRouter.ExactInputSingleParams memory params = + ISwapRouter.ExactInputSingleParams({ + tokenIn: address(token0), + tokenOut: address(token1), + fee: poolFee, + recipient: address(this), + deadline: block.timestamp, + amountIn: amountIn, + amountOutMinimum: quoter.quoteExactInputSingle( + address(token0), + address(token0), + poolFee, + amountIn, + 0 + ), + sqrtPriceLimitX96: 0 + }); + uint256 amountOut = swapRouter.exactInputSingle(params); + + // deposit token0 & token1 in vault + token0.approve(address(vault), _tokenBalance(token0)); + token1.approve(address(vault), amountOut); + + vault.deposit(_tokenBalance(token0), amountOut, 0, 0, address(this)); + + // send balance of token1, token0 and vault shares back to user + token1.safeTransfer(msg.sender, _tokenBalance(token0)); + token0.safeTransfer(msg.sender, _tokenBalance(token1)); + IERC20Metadata(address(vault)) + .safeTransfer(msg.sender, _tokenBalance(IERC20Metadata(address(vault)))); + } + + function _tokenBalance(IERC20Metadata token) internal view returns (uint256) { + return token.balanceOf(address(this)); + } + + modifier minimumAmount(uint256 amountIn) { + require(amountIn > 0, "amountIn not sufficient"); + _; + } +} \ No newline at end of file From 68b5f5962fe1cd647f3203af9b811b4e25cca71f Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Fri, 17 Sep 2021 16:56:13 +0530 Subject: [PATCH 2/9] bugfixes on Periphery.sol, deploy.js script & Multicall.sol --- .gitignore | 1 + contracts/Multicall.sol | 28 +++++++++++ contracts/Periphery.sol | 25 +++++----- contracts/interfaces/IMulticall.sol | 13 +++++ hardhat.config.js | 19 +++++--- scripts/deploy.js | 76 +++++++++++++++++++++++------ 6 files changed, 130 insertions(+), 32 deletions(-) create mode 100644 contracts/Multicall.sol create mode 100644 contracts/interfaces/IMulticall.sol diff --git a/.gitignore b/.gitignore index 271106e..b6edd69 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules #Hardhat files cache artifacts +secrets.js .DS_Store diff --git a/contracts/Multicall.sol b/contracts/Multicall.sol new file mode 100644 index 0000000..adcd52d --- /dev/null +++ b/contracts/Multicall.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +import './interfaces/IMulticall.sol'; + +/// @title Multicall +/// @notice Enables calling multiple methods in a single call to the contract +contract Multicall is IMulticall { + /// @inheritdoc IMulticall + function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} \ No newline at end of file diff --git a/contracts/Periphery.sol b/contracts/Periphery.sol index d19ea14..60c118f 100644 --- a/contracts/Periphery.sol +++ b/contracts/Periphery.sol @@ -16,18 +16,23 @@ contract Periphery { ISwapRouter public immutable swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); IQuoter public immutable quoter = IQuoter(0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6); + IVault public vault; + + constructor(IVault _vault) { + vault = _vault; + } /// @notice Calls IVault's deposit method and sends all money back to user after transactions /// @param amountIn Value of token0 to be deposited function vaultDeposit(uint256 amountIn) external minimumAmount(amountIn) { - IVault vault = IVault(0x0); uint24 poolFee = vault.pool().fee(); IERC20Metadata token0 = vault.token0(); IERC20Metadata token1 = vault.token1(); + uint256 amountToSwap = amountIn/2; // transfer token0 from sender to contract & approve router to spend it token0.safeTransferFrom(msg.sender, address(this), amountIn); - token0.approve(address(swapRouter), amountIn); + token0.approve(address(swapRouter), amountToSwap); // swap token0 for token1 ISwapRouter.ExactInputSingleParams memory params = @@ -37,12 +42,12 @@ contract Periphery { fee: poolFee, recipient: address(this), deadline: block.timestamp, - amountIn: amountIn, + amountIn: amountToSwap, amountOutMinimum: quoter.quoteExactInputSingle( address(token0), - address(token0), + address(token1), poolFee, - amountIn, + amountToSwap, 0 ), sqrtPriceLimitX96: 0 @@ -53,13 +58,11 @@ contract Periphery { token0.approve(address(vault), _tokenBalance(token0)); token1.approve(address(vault), amountOut); - vault.deposit(_tokenBalance(token0), amountOut, 0, 0, address(this)); + vault.deposit(amountToSwap, amountOut, 0, 0, msg.sender); - // send balance of token1, token0 and vault shares back to user - token1.safeTransfer(msg.sender, _tokenBalance(token0)); - token0.safeTransfer(msg.sender, _tokenBalance(token1)); - IERC20Metadata(address(vault)) - .safeTransfer(msg.sender, _tokenBalance(IERC20Metadata(address(vault)))); + // send balance of token1 & token0 to user + token0.safeTransfer(msg.sender, _tokenBalance(token0)); + token1.safeTransfer(msg.sender, _tokenBalance(token1)); } function _tokenBalance(IERC20Metadata token) internal view returns (uint256) { diff --git a/contracts/interfaces/IMulticall.sol b/contracts/interfaces/IMulticall.sol new file mode 100644 index 0000000..b11d634 --- /dev/null +++ b/contracts/interfaces/IMulticall.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + /// @dev The `msg.value` should not be trusted for any method callable from multicall. + /// @param data The encoded function data for each of the calls to make to this contract + /// @return results The results from each of the calls passed in via data + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); +} \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index 900f9ce..f02bda4 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -1,17 +1,24 @@ require("@nomiclabs/hardhat-waffle"); -require('hardhat-contract-sizer'); +require("hardhat-contract-sizer"); require("@nomiclabs/hardhat-etherscan"); require("solidity-coverage"); - +const secrets = require("./secrets"); module.exports = { solidity: { version: "0.7.5", settings: { - optimizer: { - enabled: true, - runs: 50 - } + optimizer: { + enabled: true, + runs: 50 + } } }, + networks: { + hardhat: { + forking: { + url: `https://eth-kovan.alchemyapi.io/v2/${secrets.alchemyAPIKey}` + } + } + } }; diff --git a/scripts/deploy.js b/scripts/deploy.js index 200bfba..ac2a02b 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -1,19 +1,21 @@ const hre = require("hardhat"); -const fs = require('fs'); - -const etherscan_verify = true; +const { ethers } = require("hardhat"); +const etherscan_verify = false; const POOL_ADDRESS = "0x2BFD0C3169A8C0Cd7B814D38533A0645368F0D80"; const STRATEGY_MANAGER_ADDRESS = "0x0405d9d1443DFB051D5e8E231e41C911Dc8393a4"; +const IMPERSONATING_ACCOUNT = "0xE177DdEa55d5A724515AF1D909a36543cBC4d93E"; async function main() { + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [IMPERSONATING_ACCOUNT] + }); const Factory = await hre.ethers.getContractFactory("Factory"); const factory = await Factory.deploy(); - const accounts = await hre.ethers.getSigners(); - await factory.deployed(); console.log("Factory deployed to:", factory.address); @@ -28,34 +30,78 @@ async function main() { console.log("Router deployed to:", router.address); - tx = await factory.createVault(POOL_ADDRESS, STRATEGY_MANAGER_ADDRESS, 5000, 7000, 0); + tx = await factory.createVault( + POOL_ADDRESS, + STRATEGY_MANAGER_ADDRESS, + 5000, + 7000, + 0 + ); await tx.wait(); const vaultAddress = await factory.managerVault(STRATEGY_MANAGER_ADDRESS); console.log("Vault deployed to:", vaultAddress); + const Periphery = await hre.ethers.getContractFactory("Periphery"); + const periphery = await Periphery.deploy(vaultAddress); + + await periphery.deployed(); + + console.log("Periphery deployed to:", periphery.address); + + const signer = await ethers.provider.getSigner(IMPERSONATING_ACCOUNT); + + const vault = await ethers.getContractAt("IVault", vaultAddress); + const token0Addr = await vault.token0(); + const token1Addr = await vault.token1(); + + const token0 = await ethers.getContractAt("IERC20Metadata", token0Addr); + const token1 = await ethers.getContractAt("IERC20Metadata", token1Addr); + + let token0Bal = await token0.balanceOf(signer._address); + let token1Bal = await token1.balanceOf(signer._address); + let vaultBal = await vault.balanceOf(signer._address); + + console.log( + "Token balance before call", + token0Bal.toString(), + token1Bal.toString(), + vaultBal.toString() + ); + + await token0.connect(signer).approve(periphery.address, token0Bal); + await periphery.connect(signer).vaultDeposit(token0Bal); + + token0Bal = await token0.balanceOf(signer._address); + token1Bal = await token1.balanceOf(signer._address); + vaultBal = await vault.balanceOf(signer._address); + + console.log( + "Token balance after call", + token0Bal.toString(), + token1Bal.toString(), + vaultBal.toString() + ); + if (etherscan_verify) { await hre.run("verify:verify", { - address: factory.address, - constructorArguments: [], + address: factory.address, + constructorArguments: [] }); await hre.run("verify:verify", { - address: periphery.address, - constructorArguments: [factory.address], + address: periphery.address, + constructorArguments: [factory.address] }); await hre.run("verify:verify", { - address: vaultAddress, - constructorArguments: [POOL_ADDRESS, 5000, 7000, 0], + address: vaultAddress, + constructorArguments: [POOL_ADDRESS, 5000, 7000, 0] }); - } - } - main() .then(() => process.exit(0)) .catch((error) => { From 5864ead75cd34a28d1bffb90d3c512f2fa202982 Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Fri, 17 Sep 2021 17:33:41 +0530 Subject: [PATCH 3/9] feature: vaultWithdraw() function in Periphery.sol --- contracts/Periphery.sol | 53 ++++++++++++++++++++++++++++++++++++++--- contracts/Vault.sol | 1 + 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/contracts/Periphery.sol b/contracts/Periphery.sol index 60c118f..2fca7be 100644 --- a/contracts/Periphery.sol +++ b/contracts/Periphery.sol @@ -5,30 +5,45 @@ pragma abicoder v2; import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol'; import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol'; import '@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol'; +import "@openzeppelin/contracts/math/SafeMath.sol"; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "hardhat/console.sol"; import './interfaces/IVault.sol'; import "./interfaces/IERC20Metadata.sol"; +import "./libraries/LongMath.sol"; contract Periphery { + using SafeMath for uint256; + using LongMath for uint256; using SafeERC20 for IERC20Metadata; + using SafeERC20 for IVault; ISwapRouter public immutable swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); IQuoter public immutable quoter = IQuoter(0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6); + IVault public vault; + uint24 public poolFee; + IERC20Metadata public token0; + IERC20Metadata public token1; constructor(IVault _vault) { vault = _vault; + poolFee = vault.pool().fee(); + token0 = vault.token0(); + token1 = vault.token1(); } /// @notice Calls IVault's deposit method and sends all money back to user after transactions /// @param amountIn Value of token0 to be deposited function vaultDeposit(uint256 amountIn) external minimumAmount(amountIn) { - uint24 poolFee = vault.pool().fee(); - IERC20Metadata token0 = vault.token0(); - IERC20Metadata token1 = vault.token1(); + // (uint256 token0InVault, uint256 token1InVault) = vault.getTotalAmounts(); + uint256 token0InVault = 2; + uint256 token1InVault = 4; + uint256 tokensRatio = token0InVault.mul(100).div(token1InVault); uint256 amountToSwap = amountIn/2; + console.log("Ratio", tokensRatio); // transfer token0 from sender to contract & approve router to spend it token0.safeTransferFrom(msg.sender, address(this), amountIn); @@ -65,6 +80,38 @@ contract Periphery { token1.safeTransfer(msg.sender, _tokenBalance(token1)); } + function vaultWithdraw(uint256 shares) external minimumAmount(shares) { + // transfer shares from msg.sender & withdraw + vault.safeTransferFrom(msg.sender, address(this), shares); + (uint256 amount0, uint256 amount1) = vault.withdraw(shares, 0, 0, address(this)); + + token1.approve(address(swapRouter), amount1); + + // swap token0 for token1 + ISwapRouter.ExactInputSingleParams memory params = + ISwapRouter.ExactInputSingleParams({ + tokenIn: address(token1), + tokenOut: address(token0), + fee: poolFee, + recipient: address(this), + deadline: block.timestamp, + amountIn: amount1, + amountOutMinimum: quoter.quoteExactInputSingle( + address(token1), + address(token0), + poolFee, + amount1, + 0 + ), + sqrtPriceLimitX96: 0 + }); + uint256 amountOut = swapRouter.exactInputSingle(params); + + // send balance of token1 & token0 to user + token0.safeTransfer(msg.sender, _tokenBalance(token0)); + token1.safeTransfer(msg.sender, _tokenBalance(token1)); + } + function _tokenBalance(IERC20Metadata token) internal view returns (uint256) { return token.balanceOf(address(this)); } diff --git a/contracts/Vault.sol b/contracts/Vault.sol index 5ca7a79..3afd79c 100644 --- a/contracts/Vault.sol +++ b/contracts/Vault.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: UNLICENSED pragma solidity >=0.7.5; pragma abicoder v2; From ba793d5e67feb363b90886ad0b9b27b04bd97971 Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Fri, 17 Sep 2021 18:55:32 +0530 Subject: [PATCH 4/9] feature: calculate amount to swap in ratio of vault balances in vaultDeposit() --- contracts/Periphery.sol | 22 +++++++++++------ scripts/deploy.js | 54 +++++++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/contracts/Periphery.sol b/contracts/Periphery.sol index 2fca7be..4cce641 100644 --- a/contracts/Periphery.sol +++ b/contracts/Periphery.sol @@ -8,7 +8,6 @@ import '@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol'; import "@openzeppelin/contracts/math/SafeMath.sol"; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import "hardhat/console.sol"; import './interfaces/IVault.sol'; import "./interfaces/IERC20Metadata.sol"; @@ -38,12 +37,19 @@ contract Periphery { /// @notice Calls IVault's deposit method and sends all money back to user after transactions /// @param amountIn Value of token0 to be deposited function vaultDeposit(uint256 amountIn) external minimumAmount(amountIn) { - // (uint256 token0InVault, uint256 token1InVault) = vault.getTotalAmounts(); - uint256 token0InVault = 2; - uint256 token1InVault = 4; - uint256 tokensRatio = token0InVault.mul(100).div(token1InVault); - uint256 amountToSwap = amountIn/2; - console.log("Ratio", tokensRatio); + // Calculate amount to swap based on tokens in vault + // token0 / token1 = k + // token0 + token1 = amountIn + uint256 amountToSwap; + (uint256 token0InVault, uint256 token1InVault) = vault.getTotalAmounts(); + + if(token0InVault == 0 || token1InVault == 0) { + amountToSwap = amountIn/2; + } else { + uint256 tokensRatio = token1InVault.mul(100).div(token0InVault); + uint256 token0ToKeep = amountIn.mul(100*100).div(tokensRatio.add(1*100)); + amountToSwap = (amountIn.mul(100) - token0ToKeep).div(100); + } // transfer token0 from sender to contract & approve router to spend it token0.safeTransferFrom(msg.sender, address(this), amountIn); @@ -73,7 +79,7 @@ contract Periphery { token0.approve(address(vault), _tokenBalance(token0)); token1.approve(address(vault), amountOut); - vault.deposit(amountToSwap, amountOut, 0, 0, msg.sender); + vault.deposit(_tokenBalance(token0), amountOut, 0, 0, msg.sender); // send balance of token1 & token0 to user token0.safeTransfer(msg.sender, _tokenBalance(token0)); diff --git a/scripts/deploy.js b/scripts/deploy.js index ac2a02b..9a05821 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -1,3 +1,4 @@ +const { BigNumber } = require("@ethersproject/bignumber"); const hre = require("hardhat"); const { ethers } = require("hardhat"); @@ -59,30 +60,35 @@ async function main() { const token0 = await ethers.getContractAt("IERC20Metadata", token0Addr); const token1 = await ethers.getContractAt("IERC20Metadata", token1Addr); - let token0Bal = await token0.balanceOf(signer._address); - let token1Bal = await token1.balanceOf(signer._address); - let vaultBal = await vault.balanceOf(signer._address); - - console.log( - "Token balance before call", - token0Bal.toString(), - token1Bal.toString(), - vaultBal.toString() - ); - - await token0.connect(signer).approve(periphery.address, token0Bal); - await periphery.connect(signer).vaultDeposit(token0Bal); - - token0Bal = await token0.balanceOf(signer._address); - token1Bal = await token1.balanceOf(signer._address); - vaultBal = await vault.balanceOf(signer._address); - - console.log( - "Token balance after call", - token0Bal.toString(), - token1Bal.toString(), - vaultBal.toString() - ); + for (let i = 0; i < 2; i++) { + let token0Bal = await token0.balanceOf(signer._address); + let token1Bal = await token1.balanceOf(signer._address); + let vaultBal = await vault.balanceOf(signer._address); + let transactAmt = token0Bal.div(i == 1 ? 1 : 2); + + console.log( + "before call", + token0Bal.toString(), + transactAmt.toString(), + token1Bal.toString(), + vaultBal.toString() + ); + + await token0.connect(signer).approve(periphery.address, transactAmt); + await periphery.connect(signer).vaultDeposit(transactAmt); + + token0Bal = await token0.balanceOf(signer._address); + token1Bal = await token1.balanceOf(signer._address); + vaultBal = await vault.balanceOf(signer._address); + + console.log( + "Token balance after call", + token0Bal.toString(), + transactAmt.toString(), + token1Bal.toString(), + vaultBal.toString() + ); + } if (etherscan_verify) { await hre.run("verify:verify", { From 5dbe20442015e0147d8e06a2cd59725f26a3313d Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Fri, 17 Sep 2021 19:12:29 +0530 Subject: [PATCH 5/9] withdrawing script in deploy.js --- scripts/deploy.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/scripts/deploy.js b/scripts/deploy.js index 9a05821..d93cbf0 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -60,11 +60,15 @@ async function main() { const token0 = await ethers.getContractAt("IERC20Metadata", token0Addr); const token1 = await ethers.getContractAt("IERC20Metadata", token1Addr); + var token0Bal; + var token1Bal; + var vaultBal; + for (let i = 0; i < 2; i++) { - let token0Bal = await token0.balanceOf(signer._address); - let token1Bal = await token1.balanceOf(signer._address); - let vaultBal = await vault.balanceOf(signer._address); - let transactAmt = token0Bal.div(i == 1 ? 1 : 2); + token0Bal = await token0.balanceOf(signer._address); + token1Bal = await token1.balanceOf(signer._address); + vaultBal = await vault.balanceOf(signer._address); + transactAmt = token0Bal.div(i == 1 ? 1 : 2); console.log( "before call", @@ -90,6 +94,20 @@ async function main() { ); } + await vault.connect(signer).approve(periphery.address, vaultBal); + await periphery.connect(signer).vaultWithdraw(vaultBal); + + token0Bal = await token0.balanceOf(signer._address); + token1Bal = await token1.balanceOf(signer._address); + vaultBal = await vault.balanceOf(signer._address); + + console.log( + "Token balance after withdraw", + token0Bal.toString(), + token1Bal.toString(), + vaultBal.toString() + ); + if (etherscan_verify) { await hre.run("verify:verify", { address: factory.address, From 2c22b803ceffb4a2bf55267f288460ded10c1f14 Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Fri, 17 Sep 2021 19:36:04 +0530 Subject: [PATCH 6/9] docs: IPeriphery.sol created --- contracts/Periphery.sol | 26 ++++++++++++++++---------- contracts/interfaces/IPeriphery.sol | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 contracts/interfaces/IPeriphery.sol diff --git a/contracts/Periphery.sol b/contracts/Periphery.sol index 4cce641..ca7e905 100644 --- a/contracts/Periphery.sol +++ b/contracts/Periphery.sol @@ -2,18 +2,20 @@ pragma solidity >=0.7.5; pragma abicoder v2; -import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol'; -import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol'; -import '@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol'; +import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; +import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; +import "@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol"; import "@openzeppelin/contracts/math/SafeMath.sol"; -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import './interfaces/IVault.sol'; +import "./interfaces/IPeriphery.sol"; +import "./interfaces/IVault.sol"; import "./interfaces/IERC20Metadata.sol"; import "./libraries/LongMath.sol"; -contract Periphery { +/// @title Periphery +contract Periphery is IPeriphery { using SafeMath for uint256; using LongMath for uint256; using SafeERC20 for IERC20Metadata; @@ -34,9 +36,8 @@ contract Periphery { token1 = vault.token1(); } - /// @notice Calls IVault's deposit method and sends all money back to user after transactions - /// @param amountIn Value of token0 to be deposited - function vaultDeposit(uint256 amountIn) external minimumAmount(amountIn) { + /// @inheritdoc IPeriphery + function vaultDeposit(uint256 amountIn) external override minimumAmount(amountIn) { // Calculate amount to swap based on tokens in vault // token0 / token1 = k // token0 + token1 = amountIn @@ -86,7 +87,8 @@ contract Periphery { token1.safeTransfer(msg.sender, _tokenBalance(token1)); } - function vaultWithdraw(uint256 shares) external minimumAmount(shares) { + /// @inheritdoc IPeriphery + function vaultWithdraw(uint256 shares) external override minimumAmount(shares) { // transfer shares from msg.sender & withdraw vault.safeTransferFrom(msg.sender, address(this), shares); (uint256 amount0, uint256 amount1) = vault.withdraw(shares, 0, 0, address(this)); @@ -118,6 +120,10 @@ contract Periphery { token1.safeTransfer(msg.sender, _tokenBalance(token1)); } + /** + * @notice Get the balance of a token in contract + * @param token token whose balance needs to be returned + */ function _tokenBalance(IERC20Metadata token) internal view returns (uint256) { return token.balanceOf(address(this)); } diff --git a/contracts/interfaces/IPeriphery.sol b/contracts/interfaces/IPeriphery.sol new file mode 100644 index 0000000..e0b80dd --- /dev/null +++ b/contracts/interfaces/IPeriphery.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.7.5; +pragma abicoder v2; + +import "./IERC20Metadata.sol"; + +/** + * @title IPeriphery + * @notice A middle layer between user and Aastra Vault to process transactions + * @dev Provides an interface for Periphery + */ +interface IPeriphery { + /** + * @notice Calls IVault's deposit method and sends all money back to + * user after transactions + * @param amountIn Value of token0 to be deposited + */ + function vaultDeposit(uint256 amountIn) external; + + /** + * @notice Calls vault's withdraw function in exchange for shares + * and transfers processed token0 value to msg.sender + * @param shares Value of shares in exhange for which tokens are withdrawn + */ + function vaultWithdraw(uint256 shares) external; +} \ No newline at end of file From 0c45e60e422289e601192971d092b6604ebb0185 Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Fri, 17 Sep 2021 22:36:40 +0530 Subject: [PATCH 7/9] remove unecessary code from deploy.js and remove Multicall.sol --- contracts/Multicall.sol | 28 ----------- contracts/interfaces/IMulticall.sol | 13 ------ hardhat.config.js | 7 --- scripts/deploy.js | 72 +++-------------------------- 4 files changed, 6 insertions(+), 114 deletions(-) delete mode 100644 contracts/Multicall.sol delete mode 100644 contracts/interfaces/IMulticall.sol diff --git a/contracts/Multicall.sol b/contracts/Multicall.sol deleted file mode 100644 index adcd52d..0000000 --- a/contracts/Multicall.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.7.5; -pragma abicoder v2; - -import './interfaces/IMulticall.sol'; - -/// @title Multicall -/// @notice Enables calling multiple methods in a single call to the contract -contract Multicall is IMulticall { - /// @inheritdoc IMulticall - function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) { - results = new bytes[](data.length); - for (uint256 i = 0; i < data.length; i++) { - (bool success, bytes memory result) = address(this).delegatecall(data[i]); - - if (!success) { - // Next 5 lines from https://ethereum.stackexchange.com/a/83577 - if (result.length < 68) revert(); - assembly { - result := add(result, 0x04) - } - revert(abi.decode(result, (string))); - } - - results[i] = result; - } - } -} \ No newline at end of file diff --git a/contracts/interfaces/IMulticall.sol b/contracts/interfaces/IMulticall.sol deleted file mode 100644 index b11d634..0000000 --- a/contracts/interfaces/IMulticall.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.7.5; -pragma abicoder v2; - -/// @title Multicall interface -/// @notice Enables calling multiple methods in a single call to the contract -interface IMulticall { - /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed - /// @dev The `msg.value` should not be trusted for any method callable from multicall. - /// @param data The encoded function data for each of the calls to make to this contract - /// @return results The results from each of the calls passed in via data - function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); -} \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index f02bda4..d6ffa12 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -13,12 +13,5 @@ module.exports = { runs: 50 } } - }, - networks: { - hardhat: { - forking: { - url: `https://eth-kovan.alchemyapi.io/v2/${secrets.alchemyAPIKey}` - } - } } }; diff --git a/scripts/deploy.js b/scripts/deploy.js index d93cbf0..392d461 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -1,19 +1,11 @@ -const { BigNumber } = require("@ethersproject/bignumber"); const hre = require("hardhat"); -const { ethers } = require("hardhat"); -const etherscan_verify = false; +const etherscan_verify = true; const POOL_ADDRESS = "0x2BFD0C3169A8C0Cd7B814D38533A0645368F0D80"; const STRATEGY_MANAGER_ADDRESS = "0x0405d9d1443DFB051D5e8E231e41C911Dc8393a4"; -const IMPERSONATING_ACCOUNT = "0xE177DdEa55d5A724515AF1D909a36543cBC4d93E"; async function main() { - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [IMPERSONATING_ACCOUNT] - }); - const Factory = await hre.ethers.getContractFactory("Factory"); const factory = await Factory.deploy(); @@ -51,63 +43,6 @@ async function main() { console.log("Periphery deployed to:", periphery.address); - const signer = await ethers.provider.getSigner(IMPERSONATING_ACCOUNT); - - const vault = await ethers.getContractAt("IVault", vaultAddress); - const token0Addr = await vault.token0(); - const token1Addr = await vault.token1(); - - const token0 = await ethers.getContractAt("IERC20Metadata", token0Addr); - const token1 = await ethers.getContractAt("IERC20Metadata", token1Addr); - - var token0Bal; - var token1Bal; - var vaultBal; - - for (let i = 0; i < 2; i++) { - token0Bal = await token0.balanceOf(signer._address); - token1Bal = await token1.balanceOf(signer._address); - vaultBal = await vault.balanceOf(signer._address); - transactAmt = token0Bal.div(i == 1 ? 1 : 2); - - console.log( - "before call", - token0Bal.toString(), - transactAmt.toString(), - token1Bal.toString(), - vaultBal.toString() - ); - - await token0.connect(signer).approve(periphery.address, transactAmt); - await periphery.connect(signer).vaultDeposit(transactAmt); - - token0Bal = await token0.balanceOf(signer._address); - token1Bal = await token1.balanceOf(signer._address); - vaultBal = await vault.balanceOf(signer._address); - - console.log( - "Token balance after call", - token0Bal.toString(), - transactAmt.toString(), - token1Bal.toString(), - vaultBal.toString() - ); - } - - await vault.connect(signer).approve(periphery.address, vaultBal); - await periphery.connect(signer).vaultWithdraw(vaultBal); - - token0Bal = await token0.balanceOf(signer._address); - token1Bal = await token1.balanceOf(signer._address); - vaultBal = await vault.balanceOf(signer._address); - - console.log( - "Token balance after withdraw", - token0Bal.toString(), - token1Bal.toString(), - vaultBal.toString() - ); - if (etherscan_verify) { await hre.run("verify:verify", { address: factory.address, @@ -123,6 +58,11 @@ async function main() { address: vaultAddress, constructorArguments: [POOL_ADDRESS, 5000, 7000, 0] }); + + await hre.run("verify:verify", { + address: periphery.address, + constructorArguments: [vaultAddress] + }); } } From 5211c7ee53dfd048bf0125824bf6e2685813ebfd Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Sat, 18 Sep 2021 17:03:02 +0530 Subject: [PATCH 8/9] feature: account for slippage --- contracts/Periphery.sol | 20 ++++++++++++-------- contracts/interfaces/IPeriphery.sol | 3 ++- hardhat.config.js | 14 +++++++++++++- scripts/deploy.js | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/contracts/Periphery.sol b/contracts/Periphery.sol index ca7e905..50e10e9 100644 --- a/contracts/Periphery.sol +++ b/contracts/Periphery.sol @@ -37,7 +37,9 @@ contract Periphery is IPeriphery { } /// @inheritdoc IPeriphery - function vaultDeposit(uint256 amountIn) external override minimumAmount(amountIn) { + function vaultDeposit(uint256 amountIn, uint256 slippage) external override minimumAmount(amountIn) { + require(slippage <= 100*100, "100% slippage is not allowed"); + // Calculate amount to swap based on tokens in vault // token0 / token1 = k // token0 + token1 = amountIn @@ -57,6 +59,14 @@ contract Periphery is IPeriphery { token0.approve(address(swapRouter), amountToSwap); // swap token0 for token1 + uint256 amountOutQuoted = quoter.quoteExactInputSingle( + address(token0), + address(token1), + poolFee, + amountToSwap, + 0 + ); + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ tokenIn: address(token0), @@ -65,13 +75,7 @@ contract Periphery is IPeriphery { recipient: address(this), deadline: block.timestamp, amountIn: amountToSwap, - amountOutMinimum: quoter.quoteExactInputSingle( - address(token0), - address(token1), - poolFee, - amountToSwap, - 0 - ), + amountOutMinimum: amountOutQuoted.mul(100*100 - slippage).div(100*100), sqrtPriceLimitX96: 0 }); uint256 amountOut = swapRouter.exactInputSingle(params); diff --git a/contracts/interfaces/IPeriphery.sol b/contracts/interfaces/IPeriphery.sol index e0b80dd..356f82b 100644 --- a/contracts/interfaces/IPeriphery.sol +++ b/contracts/interfaces/IPeriphery.sol @@ -14,8 +14,9 @@ interface IPeriphery { * @notice Calls IVault's deposit method and sends all money back to * user after transactions * @param amountIn Value of token0 to be deposited + * @param slippage Value in percentage of allowed slippage (2 digit precision) */ - function vaultDeposit(uint256 amountIn) external; + function vaultDeposit(uint256 amountIn, uint256 slippage) external; /** * @notice Calls vault's withdraw function in exchange for shares diff --git a/hardhat.config.js b/hardhat.config.js index d6ffa12..40b0b93 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -13,5 +13,17 @@ module.exports = { runs: 50 } } - } + }, + networks: { + hardhat: { + forking: { + url: `https://eth-kovan.alchemyapi.io/v2/${secrets.alchemyAPIKey}` + } + }, + kovan: { + url: `https://eth-kovan.alchemyapi.io/v2/${secrets.alchemyAPIKey}`, + accounts: [`0x${secrets.privateKey}`] + } + }, + etherscan: { apiKey: secrets.etherscanKey } }; diff --git a/scripts/deploy.js b/scripts/deploy.js index 392d461..eb9a26f 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -50,7 +50,7 @@ async function main() { }); await hre.run("verify:verify", { - address: periphery.address, + address: router.address, constructorArguments: [factory.address] }); From eb6f2282fb0f003b6b661544247c66aacdb3b0c4 Mon Sep 17 00:00:00 2001 From: pradeep-selva Date: Sun, 19 Sep 2021 01:56:22 +0530 Subject: [PATCH 9/9] docs: minor change --- contracts/Periphery.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/Periphery.sol b/contracts/Periphery.sol index 50e10e9..8074408 100644 --- a/contracts/Periphery.sol +++ b/contracts/Periphery.sol @@ -127,6 +127,7 @@ contract Periphery is IPeriphery { /** * @notice Get the balance of a token in contract * @param token token whose balance needs to be returned + * @return balance of a token in contract */ function _tokenBalance(IERC20Metadata token) internal view returns (uint256) { return token.balanceOf(address(this));