Skip to content

Commit

Permalink
Flexible SaveWrapper
Browse files Browse the repository at this point in the history
- Make SaveWrapper stateless (that is, aside from being `Ownable`) so it can be flexible about assets supported
- Define pattern for discrete Hardhat tasks with params
- Disable some eslint rules
  • Loading branch information
JamesLefrere committed Apr 3, 2021
1 parent fed3f2a commit 247afab
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 94 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
Expand Up @@ -26,7 +26,9 @@ module.exports = {
},
"rules": {
"@typescript-eslint/no-use-before-define": 1,
"import/no-extraneous-dependencies": 1,
"import/no-extraneous-dependencies": "off",
"no-console": "off",
"import/prefer-default-export": "off",
"no-nested-ternary": 1,
"@typescript-eslint/dot-notation": 1
},
Expand Down
235 changes: 158 additions & 77 deletions contracts/savings/peripheral/SaveWrapper.sol
Expand Up @@ -6,10 +6,11 @@ import { IMasset } from "../../interfaces/IMasset.sol";

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { IUniswapV2Router02 } from "../../interfaces/IUniswapV2Router02.sol";
import { IBasicToken } from "../../shared/IBasicToken.sol";


interface IBoostedSavingsVault {
function stake(address _beneficiary, uint256 _amount) external;
}
Expand All @@ -18,125 +19,205 @@ interface IBoostedSavingsVault {
// 0 - SAVE
// 1 - MINT AND SAVE
// 2 - BUY AND SAVE (ETH via Uni)
contract SaveWrapper {

contract SaveWrapper is Ownable {
using SafeERC20 for IERC20;

// Constants - add to bytecode during deployment
address public immutable save;
address public immutable vault;
address public immutable mAsset;

IUniswapV2Router02 public immutable uniswap;

constructor(
/**
* @dev 0. Simply saves an mAsset and then into the vault
* @param _mAsset mAsset address
* @param _save Save address
* @param _vault Boosted Savings Vault address
* @param _amount Units of mAsset to deposit to savings
*/
function saveAndStake(
address _mAsset,
address _save,
address _vault,
address _mAsset,
address[] memory _bAssets,
address _uniswapAddress
) {
require(_save != address(0), "Invalid save address");
save = _save;
require(_vault != address(0), "Invalid vault address");
vault = _vault;
require(_mAsset != address(0), "Invalid mAsset address");
mAsset = _mAsset;
require(_uniswapAddress != address(0), "Invalid uniswap address");
uniswap = IUniswapV2Router02(_uniswapAddress);

IERC20(_mAsset).safeApprove(_save, 2**256 - 1);
IERC20(_save).approve(_vault, 2**256 - 1);
for(uint256 i = 0; i < _bAssets.length; i++ ) {
IERC20(_bAssets[i]).safeApprove(_mAsset, 2**256 - 1);
}
}
uint256 _amount
) external {
require(_mAsset != address(0), "Invalid mAsset");
require(_save != address(0), "Invalid save");
require(_vault != address(0), "Invalid vault");

// 1. Get the input mAsset
IERC20(_mAsset).transferFrom(msg.sender, address(this), _amount);

/**
* @dev 0. Simply saves an mAsset and then into the vault
* @param _amount Units of mAsset to deposit to savings
*/
function saveAndStake(uint256 _amount) external {
IERC20(mAsset).transferFrom(msg.sender, address(this), _amount);
_saveAndStake(_amount, true);
// 2. Mint imAsset and stake in vault
_saveAndStake(_save, _vault, _amount, true);
}

/**
* @dev 1. Mints an mAsset and then deposits to SAVE
* @param _mAsset mAsset address
* @param _bAsset bAsset address
* @param _amt Amount of bAsset to mint with
* @param _save Save address
* @param _vault Boosted Savings Vault address
* @param _amount Amount of bAsset to mint with
* @param _minOut Min amount of mAsset to get back
* @param _stake Add the imUSD to the Savings Vault?
* @param _stake Add the imAsset to the Boosted Savings Vault?
*/
function saveViaMint(address _bAsset, uint256 _amt, uint256 _minOut, bool _stake) external {
function saveViaMint(
address _mAsset,
address _save,
address _vault,
address _bAsset,
uint256 _amount,
uint256 _minOut,
bool _stake
) external {
require(_mAsset != address(0), "Invalid mAsset");
require(_save != address(0), "Invalid save");
require(_vault != address(0), "Invalid vault");
require(_bAsset != address(0), "Invalid bAsset");

// 1. Get the input bAsset
IERC20(_bAsset).transferFrom(msg.sender, address(this), _amt);
IERC20(_bAsset).transferFrom(msg.sender, address(this), _amount);

// 2. Mint
IMasset mAsset_ = IMasset(mAsset);
uint256 massetsMinted = mAsset_.mint(_bAsset, _amt, _minOut, address(this));
// 3. Mint imUSD and optionally stake in vault
_saveAndStake(massetsMinted, _stake);
}
uint256 massetsMinted = IMasset(_mAsset).mint(_bAsset, _amount, _minOut, address(this));

// 3. Mint imAsset and optionally stake in vault
_saveAndStake(_save, _vault, massetsMinted, _stake);
}

/**
* @dev 2. Buys a bAsset on Uniswap with ETH then mUSD on Curve
* @param _amountOutMin Min uniswap output in bAsset units
* @param _path Sell path on Uniswap (e.g. [WETH, DAI])
* @param _minOutMStable Min amount of mUSD to receive
* @param _stake Add the imUSD to the Savings Vault?
* @dev 2. Buys a bAsset on Uniswap with ETH, then mints imAsset via mAsset,
* optionally staking in the Boosted Savings Vault
* @param _mAsset mAsset address
* @param _save Save address
* @param _vault Boosted vault address
* @param _uniswap Uniswap router address
* @param _amountOutMin Min uniswap output in bAsset units
* @param _path Sell path on Uniswap (e.g. [WETH, DAI])
* @param _minOutMStable Min amount of mAsset to receive
* @param _stake Add the imAsset to the Savings Vault?
*/
function saveViaUniswapETH(
address _mAsset,
address _save,
address _vault,
address _uniswap,
uint256 _amountOutMin,
address[] calldata _path,
uint256 _minOutMStable,
bool _stake
) external payable {
require(_mAsset != address(0), "Invalid mAsset");
require(_save != address(0), "Invalid save");
require(_vault != address(0), "Invalid vault");
require(_uniswap != address(0), "Invalid uniswap");

// 1. Get the bAsset
uint[] memory amounts = uniswap.swapExactETHForTokens{value: msg.value}(
_amountOutMin,
_path,
address(this),
block.timestamp + 1000
);
// 2. Purchase mUSD
uint256 massetsMinted = IMasset(mAsset).mint(_path[_path.length-1], amounts[amounts.length-1], _minOutMStable, address(this));
// 3. Mint imUSD and optionally stake in vault
_saveAndStake(massetsMinted, _stake);
uint256[] memory amounts =
IUniswapV2Router02(_uniswap).swapExactETHForTokens{ value: msg.value }(
_amountOutMin,
_path,
address(this),
block.timestamp + 1000
);

// 2. Purchase mAsset
uint256 massetsMinted =
IMasset(_mAsset).mint(
_path[_path.length - 1],
amounts[amounts.length - 1],
_minOutMStable,
address(this)
);

// 3. Mint imAsset and optionally stake in vault
_saveAndStake(_save, _vault, massetsMinted, _stake);
}

/**
* @dev Gets estimated mAsset output from a WETH > bAsset > mAsset trade
* @param _mAsset mAsset address
* @param _uniswap Uniswap router address
* @param _ethAmount ETH amount to sell
* @param _path Sell path on Uniswap (e.g. [WETH, DAI])
*/
function estimate_saveViaUniswapETH(
address _mAsset,
address _uniswap,
uint256 _ethAmount,
address[] calldata _path
)
external
view
returns (uint256 out)
{
uint256 estimatedBasset = _getAmountOut(_ethAmount, _path);
return IMasset(mAsset).getMintOutput(_path[_path.length-1], estimatedBasset);
) external view returns (uint256 out) {
require(_mAsset != address(0), "Invalid mAsset");
require(_uniswap != address(0), "Invalid uniswap");

uint256 estimatedBasset = _getAmountOut(_uniswap, _ethAmount, _path);
return IMasset(_mAsset).getMintOutput(_path[_path.length - 1], estimatedBasset);
}

/** @dev Internal func to deposit into SAVE and optionally stake in the vault */
/** @dev Internal func to deposit into Save and optionally stake in the vault
* @param _save Save address
* @param _vault Boosted vault address
* @param _amount Amount of mAsset to deposit
* @param _stake Add the imAsset to the Savings Vault?
*/
function _saveAndStake(
address _save,
address _vault,
uint256 _amount,
bool _stake
) internal {
if(_stake){
uint256 credits = ISavingsContractV2(save).depositSavings(_amount, address(this));
IBoostedSavingsVault(vault).stake(msg.sender, credits);
if (_stake) {
uint256 credits = ISavingsContractV2(_save).depositSavings(_amount, address(this));
IBoostedSavingsVault(_vault).stake(msg.sender, credits);
} else {
ISavingsContractV2(save).depositSavings(_amount, msg.sender);
ISavingsContractV2(_save).depositSavings(_amount, msg.sender);
}
}

/** @dev Internal func to get esimtated Uniswap output from WETH to token trade */
function _getAmountOut(uint256 _amountIn, address[] memory _path) internal view returns (uint256) {
uint256[] memory amountsOut = uniswap.getAmountsOut(_amountIn, _path);
/** @dev Internal func to get estimated Uniswap output from WETH to token trade */
function _getAmountOut(
address _uniswap,
uint256 _amountIn,
address[] memory _path
) internal view returns (uint256) {
uint256[] memory amountsOut = IUniswapV2Router02(_uniswap).getAmountsOut(_amountIn, _path);
return amountsOut[amountsOut.length - 1];
}
}

/**
* @dev Approve mAsset, Save and multiple bAssets
*/
function approve(
address _mAsset,
address _save,
address _vault,
address[] calldata _bAssets
) external onlyOwner {
_approve(_mAsset, _save);
_approve(_save, _vault);
_approve(_bAssets, _mAsset);
}

/**
* @dev Approve one token/spender
*/
function approve(address _token, address _spender) external onlyOwner {
_approve(_token, _spender);
}

/**
* @dev Approve multiple tokens/one spender
*/
function approve(address[] calldata _tokens, address _spender) external onlyOwner {
_approve(_tokens, _spender);
}

function _approve(address _token, address _spender) internal {
require(_spender != address(0), "Invalid spender");
require(_token != address(0), "Invalid token");
IERC20(_token).safeApprove(_spender, 2**256 - 1);
}

function _approve(address[] calldata _tokens, address _spender) internal {
require(_spender != address(0), "Invalid spender");
for (uint256 i = 0; i < _tokens.length; i++) {
require(_tokens[i] != address(0), "Invalid token");
IERC20(_tokens[i]).safeApprove(_spender, 2**256 - 1);
}
}
}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -46,6 +46,7 @@
"@typescript-eslint/eslint-plugin-tslint": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"chai": "^4.2.0",
"chalk": "^4.1.0",
"csv-parse": "^4.15.0",
"eslint": "^7.18.0",
"eslint-config-airbnb-typescript": "^12.0.0",
Expand Down
2 changes: 2 additions & 0 deletions tasks.config.ts
Expand Up @@ -5,5 +5,7 @@ import "./tasks/mBTC"
import "./tasks/deployFeeders"
import "./tasks/deployMV3"
import "./tasks/mUSD"
import "./tasks/SaveWrapper"
import "./tasks/BoostedSavingsVault"

export default config
68 changes: 68 additions & 0 deletions tasks/BoostedSavingsVault.ts
@@ -0,0 +1,68 @@
import "ts-node/register"
import "tsconfig-paths/register"
import { task, types } from "hardhat/config"
import { DEAD_ADDRESS } from "@utils/constants"

import { params } from "./taskUtils"
import { AssetProxy__factory, BoostedSavingsVault__factory } from "../types/generated"

task("BoostedSavingsVault.deploy", "Deploys a BoostedSavingsVault")
.addParam("nexus", "Nexus address", undefined, params.address, false)
.addParam("proxyAdmin", "ProxyAdmin address", undefined, params.address, false)
.addParam("rewardsDistributor", "RewardsDistributor address", undefined, params.address, false)
.addParam("stakingToken", "Staking token address", undefined, params.address, false)
.addParam("rewardsToken", "Rewards token address", undefined, params.address, false)
.addParam("vaultName", "Vault name", undefined, types.string, false)
.addParam("vaultSymbol", "Vault symbol", undefined, types.string, false)
.addParam("boostCoefficient", "Boost coefficient", (45).toString(), types.string, false)
.addParam("priceCoefficient", "Price coefficient", (1e18).toString(), types.string, false)
.setAction(
async (
{
boostCoefficient,
nexus,
priceCoefficient,
proxyAdmin,
rewardsDistributor,
rewardsToken,
vaultName,
vaultSymbol,
stakingToken,
}: {
boostCoefficient: string
nexus: string
priceCoefficient: string
proxyAdmin: string
rewardsDistributor: string
rewardsToken: string
vaultName: string
vaultSymbol: string
stakingToken: string
},
{ ethers },
) => {
const [deployer] = await ethers.getSigners()

const implementation = await new BoostedSavingsVault__factory(deployer).deploy(
nexus,
stakingToken,
DEAD_ADDRESS,
priceCoefficient,
boostCoefficient,
rewardsToken,
)
const receipt = await implementation.deployTransaction.wait()
console.log(`Deployed Vault Implementation to ${implementation.address}. gas used ${receipt.gasUsed}`)

const data = implementation.interface.encodeFunctionData("initialize", [rewardsDistributor, vaultName, vaultSymbol])

const assetProxy = await new AssetProxy__factory(deployer).deploy(implementation.address, proxyAdmin, data)
const assetProxyDeployReceipt = await assetProxy.deployTransaction.wait()

await new BoostedSavingsVault__factory(deployer).attach(assetProxy.address)

console.log(`Deployed Vault Proxy to ${assetProxy.address}. gas used ${assetProxyDeployReceipt.gasUsed}`)
},
)

export {}

0 comments on commit 247afab

Please sign in to comment.