Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Periphery.sol for interacting with Vault.sol #5

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_modules
#Hardhat files
cache
artifacts
secrets.js


.DS_Store
28 changes: 28 additions & 0 deletions contracts/Multicall.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
135 changes: 135 additions & 0 deletions contracts/Periphery.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// 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/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

import "./interfaces/IPeriphery.sol";
import "./interfaces/IVault.sol";
import "./interfaces/IERC20Metadata.sol";
import "./libraries/LongMath.sol";

/// @title Periphery
contract Periphery is IPeriphery {
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();
}

/// @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
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);
token0.approve(address(swapRouter), amountToSwap);

// 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: amountToSwap,
amountOutMinimum: quoter.quoteExactInputSingle(
address(token0),
address(token1),
poolFee,
amountToSwap,
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, msg.sender);

// send balance of token1 & token0 to user
token0.safeTransfer(msg.sender, _tokenBalance(token0));
token1.safeTransfer(msg.sender, _tokenBalance(token1));
}

/// @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));

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));
}

/**
* @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));
}

modifier minimumAmount(uint256 amountIn) {
require(amountIn > 0, "amountIn not sufficient");
_;
}
}
1 change: 1 addition & 0 deletions contracts/Vault.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.5;
pragma abicoder v2;

Expand Down
13 changes: 13 additions & 0 deletions contracts/interfaces/IMulticall.sol
Original file line number Diff line number Diff line change
@@ -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);
}
26 changes: 26 additions & 0 deletions contracts/interfaces/IPeriphery.sol
Original file line number Diff line number Diff line change
@@ -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;
}
19 changes: 13 additions & 6 deletions hardhat.config.js
Original file line number Diff line number Diff line change
@@ -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}`
}
}
}
};
100 changes: 85 additions & 15 deletions scripts/deploy.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
const { BigNumber } = require("@ethersproject/bignumber");
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);
Expand All @@ -28,34 +31,101 @@ 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);

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,
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) => {
Expand Down