diff --git a/contracts/test/yearn/yBUSD.deployed.sol b/contracts/test/yearn/yBUSD.deployed.sol new file mode 100644 index 0000000..1d5bdb7 --- /dev/null +++ b/contracts/test/yearn/yBUSD.deployed.sol @@ -0,0 +1,465 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; +//import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol"; + +import "./ERC20mod.sol"; +import "./Interfaces.sol"; +import "./DyDxStructs.sol"; + +interface IIEarnManager { + function recommend(address _token) external view returns ( + string memory choice, + uint256 capr, + uint256 iapr, + uint256 aapr, + uint256 dapr + ); +} + +contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + uint256 public pool; + address public token; + address public compound; + address public fulcrum; + address public aave; + address public aaveToken; + address public dydx; + uint256 public dToken; + address public apr; + + enum Lender { + NONE, + DYDX, + COMPOUND, + AAVE, + FULCRUM + } + + Lender public provider = Lender.NONE; + + function initialize() public initializer { + ERC20Detailed.initialize("iearn BUSD", "yBUSD", 18); + token = address(0xc7f6b2702128E2d47A4C02ced1AC23Cef2084483); + apr = address(0x9737E863fB48420928093072dF061542322C01e3); + dydx = address(0x3D248f7d805826F38D5D2000657176B9a2584455 ); + aave = address(0xEeA2F67b2c2f3a44bAC85a87f9d87265d2Ee7208 ); + fulcrum = address(0x84571139040c6aA34235D983C4461dbb039bEcB0); + aaveToken = address(0xB12620790C87482Dc5643A5B60A31877d2F00542); + compound = address(0xF57834E136986C11875BAA440c085Cb8FADE1975 ); + dToken = 5; + approveToken(); + } + + // Quick swap low gas method for pool swaps + function deposit(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = _calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = _calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + // No rebalance implementation for lower fees and faster swaps + function withdraw(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = _calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check balance + uint256 b = IERC20(token).balanceOf(address(this)); + if (b < r) { + _withdrawSome(r.sub(b)); + } + + IERC20(token).transfer(msg.sender, r); + pool = _calcPoolValueInToken(); + } + + function() external payable { + + } + + function recommend() public view returns (Lender) { + (,uint256 capr,uint256 iapr,uint256 aapr,uint256 dapr) = IIEarnManager(apr).recommend(token); + uint256 max = 0; + if (capr > max) { + max = capr; + } + if (iapr > max) { + max = iapr; + } + if (aapr > max) { + max = aapr; + } + if (dapr > max) { + max = dapr; + } + + Lender newProvider = Lender.NONE; + if (max == capr) { + newProvider = Lender.COMPOUND; + } else if (max == iapr) { + newProvider = Lender.FULCRUM; + } else if (max == aapr) { + newProvider = Lender.AAVE; + } else if (max == dapr) { + newProvider = Lender.DYDX; + } + return newProvider; + } + + function supplyDydx(uint256 amount) public returns(uint) { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(true, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Deposit; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function _withdrawDydx(uint256 amount) internal { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(false, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Withdraw; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function getAave() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPool(); + } + function getAaveCore() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPoolCore(); + } + + function approveToken() public { + IERC20(token).safeApprove(compound, uint(-1)); //also add to constructor + IERC20(token).safeApprove(dydx, uint(-1)); + IERC20(token).safeApprove(getAaveCore(), uint(-1)); + IERC20(token).safeApprove(fulcrum, uint(-1)); + } + + function balance() public view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + function balanceDydx() public view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function balanceCompound() public view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function balanceCompoundInToken() public view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function balanceFulcrumInToken() public view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function balanceFulcrum() public view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function balanceAave() public view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function _balance() internal view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + function _balanceDydx() internal view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function _balanceCompound() internal view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function _balanceCompoundInToken() internal view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function _balanceFulcrumInToken() internal view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function _balanceFulcrum() internal view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function _balanceAave() internal view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function _withdrawAll() internal { + uint256 amount = _balanceCompound(); + if (amount > 0) { + _withdrawCompound(amount); + } + amount = _balanceDydx(); + if (amount > 0) { + _withdrawDydx(amount); + } + amount = _balanceFulcrum(); + if (amount > 0) { + _withdrawFulcrum(amount); + } + amount = _balanceAave(); + if (amount > 0) { + _withdrawAave(amount); + } + } + + function _withdrawSomeCompound(uint256 _amount) internal { + uint256 b = balanceCompound(); + uint256 bT = balanceCompoundInToken(); + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawCompound(amount); + } + + // 1999999614570950845 + function _withdrawSomeFulcrum(uint256 _amount) internal { + // Balance of fulcrum tokens, 1 iDAI = 1.00x DAI + uint256 b = balanceFulcrum(); // 1970469086655766652 + // Balance of token in fulcrum + uint256 bT = balanceFulcrumInToken(); // 2000000803224344406 + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawFulcrum(amount); + } + + function _withdrawSome(uint256 _amount) internal { + if (provider == Lender.COMPOUND) { + _withdrawSomeCompound(_amount); + } + if (provider == Lender.AAVE) { + require(balanceAave() >= _amount, "insufficient funds"); + _withdrawAave(_amount); + } + if (provider == Lender.DYDX) { + require(balanceDydx() >= _amount, "insufficient funds"); + _withdrawDydx(_amount); + } + if (provider == Lender.FULCRUM) { + _withdrawSomeFulcrum(_amount); + } + } + + function rebalance() public { + Lender newProvider = recommend(); + + if (newProvider != provider) { + _withdrawAll(); + } + + if (balance() > 0) { + if (newProvider == Lender.DYDX) { + supplyDydx(balance()); + } else if (newProvider == Lender.FULCRUM) { + supplyFulcrum(balance()); + } else if (newProvider == Lender.COMPOUND) { + supplyCompound(balance()); + } else if (newProvider == Lender.AAVE) { + supplyAave(balance()); + } + } + + provider = newProvider; + } + + // Internal only rebalance for better gas in redeem + function _rebalance(Lender newProvider) internal { + if (_balance() > 0) { + if (newProvider == Lender.DYDX) { + supplyDydx(_balance()); + } else if (newProvider == Lender.FULCRUM) { + supplyFulcrum(_balance()); + } else if (newProvider == Lender.COMPOUND) { + supplyCompound(_balance()); + } else if (newProvider == Lender.AAVE) { + supplyAave(_balance()); + } + } + provider = newProvider; + } + + function supplyAave(uint amount) public { + Aave(getAave()).deposit(token, amount, 0); + } + function supplyFulcrum(uint amount) public { + require(Fulcrum(fulcrum).mint(address(this), amount) > 0, "FULCRUM: supply failed"); + } + function supplyCompound(uint amount) public { + require(Compound(compound).mint(amount) == 0, "COMPOUND: supply failed"); + } + function _withdrawAave(uint amount) internal { + AToken(aaveToken).redeem(amount); + } + function _withdrawFulcrum(uint amount) internal { + require(Fulcrum(fulcrum).burn(address(this), amount) > 0, "FULCRUM: withdraw failed"); + } + function _withdrawCompound(uint amount) internal { + require(Compound(compound).redeem(amount) == 0, "COMPOUND: withdraw failed"); + } + + function invest(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + rebalance(); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + function _calcPoolValueInToken() internal view returns (uint) { + return _balanceCompoundInToken() + .add(_balanceFulcrumInToken()) + .add(_balanceDydx()) + .add(_balanceAave()) + .add(_balance()); + } + + function calcPoolValueInToken() public view returns (uint) { + return balanceCompoundInToken() + .add(balanceFulcrumInToken()) + .add(balanceDydx()) + .add(balanceAave()) + .add(balance()); + } + + function getPricePerFullShare() public view returns (uint) { + uint _pool = calcPoolValueInToken(); + return _pool.mul(1e18).div(_totalSupply); + } + + // Redeem any invested tokens from the pool + function redeem(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check ETH balance + uint256 b = IERC20(token).balanceOf(address(this)); + Lender newProvider = provider; + if (b < r) { + newProvider = recommend(); + if (newProvider != provider) { + _withdrawAll(); + } else { + _withdrawSome(r.sub(b)); + } + } + + IERC20(token).safeTransfer(msg.sender, r); + + if (newProvider != provider) { + _rebalance(newProvider); + } + pool = calcPoolValueInToken(); + } +} \ No newline at end of file diff --git a/contracts/test/yearn/yDAI.deployed.sol b/contracts/test/yearn/yDAI.deployed.sol index 8610cd1..f845351 100644 --- a/contracts/test/yearn/yDAI.deployed.sol +++ b/contracts/test/yearn/yDAI.deployed.sol @@ -58,7 +58,7 @@ contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs { fulcrum = address(0x493C57C4763932315A328269E1ADaD09653B9081); aaveToken = address(0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d); compound = address(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); - dToken = 3; + dToken = 1; approveToken(); } diff --git a/contracts/test/yearn/yTUSD.deployed.sol b/contracts/test/yearn/yTUSD.deployed.sol new file mode 100644 index 0000000..f37ec04 --- /dev/null +++ b/contracts/test/yearn/yTUSD.deployed.sol @@ -0,0 +1,465 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; +//import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol"; + +import "./ERC20mod.sol"; +import "./Interfaces.sol"; +import "./DyDxStructs.sol"; + +interface IIEarnManager { + function recommend(address _token) external view returns ( + string memory choice, + uint256 capr, + uint256 iapr, + uint256 aapr, + uint256 dapr + ); +} + +contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + uint256 public pool; + address public token; + address public compound; + address public fulcrum; + address public aave; + address public aaveToken; + address public dydx; + uint256 public dToken; + address public apr; + + enum Lender { + NONE, + DYDX, + COMPOUND, + AAVE, + FULCRUM + } + + Lender public provider = Lender.NONE; + + function initialize() public initializer { + ERC20Detailed.initialize("iearn TUSD", "yTUSD", 18); + token = address(0x4758d574B50711bD70479b592c6712F8C57d42Ad ); + apr = address(0x9737E863fB48420928093072dF061542322C01e3); + dydx = address(0x3D248f7d805826F38D5D2000657176B9a2584455 ); + aave = address(0xEeA2F67b2c2f3a44bAC85a87f9d87265d2Ee7208 ); + fulcrum = address(0x36D7Ef0092B0702c4C2f0C80E693046c3e31F954); + aaveToken = address(0xd3C58Af295F10f4B1A7d6A60Db47fC72217cb533); + compound = address(0xB86dD17545f8edcd167F3d988817386bC21f6371 ); + dToken = 4; + approveToken(); + } + + // Quick swap low gas method for pool swaps + function deposit(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = _calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = _calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + // No rebalance implementation for lower fees and faster swaps + function withdraw(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = _calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check balance + uint256 b = IERC20(token).balanceOf(address(this)); + if (b < r) { + _withdrawSome(r.sub(b)); + } + + IERC20(token).transfer(msg.sender, r); + pool = _calcPoolValueInToken(); + } + + function() external payable { + + } + + function recommend() public view returns (Lender) { + (,uint256 capr,uint256 iapr,uint256 aapr,uint256 dapr) = IIEarnManager(apr).recommend(token); + uint256 max = 0; + if (capr > max) { + max = capr; + } + if (iapr > max) { + max = iapr; + } + if (aapr > max) { + max = aapr; + } + if (dapr > max) { + max = dapr; + } + + Lender newProvider = Lender.NONE; + if (max == capr) { + newProvider = Lender.COMPOUND; + } else if (max == iapr) { + newProvider = Lender.FULCRUM; + } else if (max == aapr) { + newProvider = Lender.AAVE; + } else if (max == dapr) { + newProvider = Lender.DYDX; + } + return newProvider; + } + + function supplyDydx(uint256 amount) public returns(uint) { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(true, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Deposit; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function _withdrawDydx(uint256 amount) internal { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(false, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Withdraw; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function getAave() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPool(); + } + function getAaveCore() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPoolCore(); + } + + function approveToken() public { + IERC20(token).safeApprove(compound, uint(-1)); //also add to constructor + IERC20(token).safeApprove(dydx, uint(-1)); + IERC20(token).safeApprove(getAaveCore(), uint(-1)); + IERC20(token).safeApprove(fulcrum, uint(-1)); + } + + function balance() public view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + function balanceDydx() public view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function balanceCompound() public view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function balanceCompoundInToken() public view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function balanceFulcrumInToken() public view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function balanceFulcrum() public view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function balanceAave() public view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function _balance() internal view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + function _balanceDydx() internal view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function _balanceCompound() internal view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function _balanceCompoundInToken() internal view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function _balanceFulcrumInToken() internal view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function _balanceFulcrum() internal view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function _balanceAave() internal view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function _withdrawAll() internal { + uint256 amount = _balanceCompound(); + if (amount > 0) { + _withdrawCompound(amount); + } + amount = _balanceDydx(); + if (amount > 0) { + _withdrawDydx(amount); + } + amount = _balanceFulcrum(); + if (amount > 0) { + _withdrawFulcrum(amount); + } + amount = _balanceAave(); + if (amount > 0) { + _withdrawAave(amount); + } + } + + function _withdrawSomeCompound(uint256 _amount) internal { + uint256 b = balanceCompound(); + uint256 bT = balanceCompoundInToken(); + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawCompound(amount); + } + + // 1999999614570950845 + function _withdrawSomeFulcrum(uint256 _amount) internal { + // Balance of fulcrum tokens, 1 iDAI = 1.00x DAI + uint256 b = balanceFulcrum(); // 1970469086655766652 + // Balance of token in fulcrum + uint256 bT = balanceFulcrumInToken(); // 2000000803224344406 + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawFulcrum(amount); + } + + function _withdrawSome(uint256 _amount) internal { + if (provider == Lender.COMPOUND) { + _withdrawSomeCompound(_amount); + } + if (provider == Lender.AAVE) { + require(balanceAave() >= _amount, "insufficient funds"); + _withdrawAave(_amount); + } + if (provider == Lender.DYDX) { + require(balanceDydx() >= _amount, "insufficient funds"); + _withdrawDydx(_amount); + } + if (provider == Lender.FULCRUM) { + _withdrawSomeFulcrum(_amount); + } + } + + function rebalance() public { + Lender newProvider = recommend(); + + if (newProvider != provider) { + _withdrawAll(); + } + + if (balance() > 0) { + if (newProvider == Lender.DYDX) { + supplyDydx(balance()); + } else if (newProvider == Lender.FULCRUM) { + supplyFulcrum(balance()); + } else if (newProvider == Lender.COMPOUND) { + supplyCompound(balance()); + } else if (newProvider == Lender.AAVE) { + supplyAave(balance()); + } + } + + provider = newProvider; + } + + // Internal only rebalance for better gas in redeem + function _rebalance(Lender newProvider) internal { + if (_balance() > 0) { + if (newProvider == Lender.DYDX) { + supplyDydx(_balance()); + } else if (newProvider == Lender.FULCRUM) { + supplyFulcrum(_balance()); + } else if (newProvider == Lender.COMPOUND) { + supplyCompound(_balance()); + } else if (newProvider == Lender.AAVE) { + supplyAave(_balance()); + } + } + provider = newProvider; + } + + function supplyAave(uint amount) public { + Aave(getAave()).deposit(token, amount, 0); + } + function supplyFulcrum(uint amount) public { + require(Fulcrum(fulcrum).mint(address(this), amount) > 0, "FULCRUM: supply failed"); + } + function supplyCompound(uint amount) public { + require(Compound(compound).mint(amount) == 0, "COMPOUND: supply failed"); + } + function _withdrawAave(uint amount) internal { + AToken(aaveToken).redeem(amount); + } + function _withdrawFulcrum(uint amount) internal { + require(Fulcrum(fulcrum).burn(address(this), amount) > 0, "FULCRUM: withdraw failed"); + } + function _withdrawCompound(uint amount) internal { + require(Compound(compound).redeem(amount) == 0, "COMPOUND: withdraw failed"); + } + + function invest(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + rebalance(); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + function _calcPoolValueInToken() internal view returns (uint) { + return _balanceCompoundInToken() + .add(_balanceFulcrumInToken()) + .add(_balanceDydx()) + .add(_balanceAave()) + .add(_balance()); + } + + function calcPoolValueInToken() public view returns (uint) { + return balanceCompoundInToken() + .add(balanceFulcrumInToken()) + .add(balanceDydx()) + .add(balanceAave()) + .add(balance()); + } + + function getPricePerFullShare() public view returns (uint) { + uint _pool = calcPoolValueInToken(); + return _pool.mul(1e18).div(_totalSupply); + } + + // Redeem any invested tokens from the pool + function redeem(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check ETH balance + uint256 b = IERC20(token).balanceOf(address(this)); + Lender newProvider = provider; + if (b < r) { + newProvider = recommend(); + if (newProvider != provider) { + _withdrawAll(); + } else { + _withdrawSome(r.sub(b)); + } + } + + IERC20(token).safeTransfer(msg.sender, r); + + if (newProvider != provider) { + _rebalance(newProvider); + } + pool = calcPoolValueInToken(); + } +} \ No newline at end of file diff --git a/contracts/test/yearn/yUSDC.deployed.sol b/contracts/test/yearn/yUSDC.deployed.sol new file mode 100644 index 0000000..f8be9ab --- /dev/null +++ b/contracts/test/yearn/yUSDC.deployed.sol @@ -0,0 +1,465 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; +//import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol"; + +import "./ERC20mod.sol"; +import "./Interfaces.sol"; +import "./DyDxStructs.sol"; + +interface IIEarnManager { + function recommend(address _token) external view returns ( + string memory choice, + uint256 capr, + uint256 iapr, + uint256 aapr, + uint256 dapr + ); +} + +contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + uint256 public pool; + address public token; + address public compound; + address public fulcrum; + address public aave; + address public aaveToken; + address public dydx; + uint256 public dToken; + address public apr; + + enum Lender { + NONE, + DYDX, + COMPOUND, + AAVE, + FULCRUM + } + + Lender public provider = Lender.NONE; + + function initialize() public initializer { + ERC20Detailed.initialize("iearn USDC", "yUSDC", 6); + token = address(0x4DBCdF9B62e891a7cec5A2568C3F4FAF9E8Abe2b); + apr = address(0x9737E863fB48420928093072dF061542322C01e3); + dydx = address(0x3D248f7d805826F38D5D2000657176B9a2584455); + aave = address(0xEeA2F67b2c2f3a44bAC85a87f9d87265d2Ee7208); + fulcrum = address(0x39852BB352c0cF3961785722d5cEE0ecc794C78B); + aaveToken = address(0xE225f00F42310354E3882de0e0c2901a281dB8Bd); + compound = address(0x5B281A6DdA0B271e91ae35DE655Ad301C976edb1); + dToken = 2; + approveToken(); + } + + // Quick swap low gas method for pool swaps + function deposit(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = _calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = _calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + // No rebalance implementation for lower fees and faster swaps + function withdraw(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = _calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check balance + uint256 b = IERC20(token).balanceOf(address(this)); + if (b < r) { + _withdrawSome(r.sub(b)); + } + + IERC20(token).transfer(msg.sender, r); + pool = _calcPoolValueInToken(); + } + + function() external payable { + + } + + function recommend() public view returns (Lender) { + (,uint256 capr,uint256 iapr,uint256 aapr,uint256 dapr) = IIEarnManager(apr).recommend(token); + uint256 max = 0; + if (capr > max) { + max = capr; + } + if (iapr > max) { + max = iapr; + } + if (aapr > max) { + max = aapr; + } + if (dapr > max) { + max = dapr; + } + + Lender newProvider = Lender.NONE; + if (max == capr) { + newProvider = Lender.COMPOUND; + } else if (max == iapr) { + newProvider = Lender.FULCRUM; + } else if (max == aapr) { + newProvider = Lender.AAVE; + } else if (max == dapr) { + newProvider = Lender.DYDX; + } + return newProvider; + } + + function supplyDydx(uint256 amount) public returns(uint) { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(true, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Deposit; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function _withdrawDydx(uint256 amount) internal { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(false, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Withdraw; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function getAave() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPool(); + } + function getAaveCore() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPoolCore(); + } + + function approveToken() public { + IERC20(token).safeApprove(compound, uint(-1)); //also add to constructor + IERC20(token).safeApprove(dydx, uint(-1)); + IERC20(token).safeApprove(getAaveCore(), uint(-1)); + IERC20(token).safeApprove(fulcrum, uint(-1)); + } + + function balance() public view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + function balanceDydx() public view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function balanceCompound() public view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function balanceCompoundInToken() public view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function balanceFulcrumInToken() public view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function balanceFulcrum() public view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function balanceAave() public view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function _balance() internal view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + function _balanceDydx() internal view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function _balanceCompound() internal view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function _balanceCompoundInToken() internal view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function _balanceFulcrumInToken() internal view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function _balanceFulcrum() internal view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function _balanceAave() internal view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function _withdrawAll() internal { + uint256 amount = _balanceCompound(); + if (amount > 0) { + _withdrawCompound(amount); + } + amount = _balanceDydx(); + if (amount > 0) { + _withdrawDydx(amount); + } + amount = _balanceFulcrum(); + if (amount > 0) { + _withdrawFulcrum(amount); + } + amount = _balanceAave(); + if (amount > 0) { + _withdrawAave(amount); + } + } + + function _withdrawSomeCompound(uint256 _amount) internal { + uint256 b = balanceCompound(); + uint256 bT = balanceCompoundInToken(); + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawCompound(amount); + } + + // 1999999614570950845 + function _withdrawSomeFulcrum(uint256 _amount) internal { + // Balance of fulcrum tokens, 1 iDAI = 1.00x DAI + uint256 b = balanceFulcrum(); // 1970469086655766652 + // Balance of token in fulcrum + uint256 bT = balanceFulcrumInToken(); // 2000000803224344406 + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawFulcrum(amount); + } + + function _withdrawSome(uint256 _amount) internal { + if (provider == Lender.COMPOUND) { + _withdrawSomeCompound(_amount); + } + if (provider == Lender.AAVE) { + require(balanceAave() >= _amount, "insufficient funds"); + _withdrawAave(_amount); + } + if (provider == Lender.DYDX) { + require(balanceDydx() >= _amount, "insufficient funds"); + _withdrawDydx(_amount); + } + if (provider == Lender.FULCRUM) { + _withdrawSomeFulcrum(_amount); + } + } + + function rebalance() public { + Lender newProvider = recommend(); + + if (newProvider != provider) { + _withdrawAll(); + } + + if (balance() > 0) { + if (newProvider == Lender.DYDX) { + supplyDydx(balance()); + } else if (newProvider == Lender.FULCRUM) { + supplyFulcrum(balance()); + } else if (newProvider == Lender.COMPOUND) { + supplyCompound(balance()); + } else if (newProvider == Lender.AAVE) { + supplyAave(balance()); + } + } + + provider = newProvider; + } + + // Internal only rebalance for better gas in redeem + function _rebalance(Lender newProvider) internal { + if (_balance() > 0) { + if (newProvider == Lender.DYDX) { + supplyDydx(_balance()); + } else if (newProvider == Lender.FULCRUM) { + supplyFulcrum(_balance()); + } else if (newProvider == Lender.COMPOUND) { + supplyCompound(_balance()); + } else if (newProvider == Lender.AAVE) { + supplyAave(_balance()); + } + } + provider = newProvider; + } + + function supplyAave(uint amount) public { + Aave(getAave()).deposit(token, amount, 0); + } + function supplyFulcrum(uint amount) public { + require(Fulcrum(fulcrum).mint(address(this), amount) > 0, "FULCRUM: supply failed"); + } + function supplyCompound(uint amount) public { + require(Compound(compound).mint(amount) == 0, "COMPOUND: supply failed"); + } + function _withdrawAave(uint amount) internal { + AToken(aaveToken).redeem(amount); + } + function _withdrawFulcrum(uint amount) internal { + require(Fulcrum(fulcrum).burn(address(this), amount) > 0, "FULCRUM: withdraw failed"); + } + function _withdrawCompound(uint amount) internal { + require(Compound(compound).redeem(amount) == 0, "COMPOUND: withdraw failed"); + } + + function invest(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + rebalance(); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + function _calcPoolValueInToken() internal view returns (uint) { + return _balanceCompoundInToken() + .add(_balanceFulcrumInToken()) + .add(_balanceDydx()) + .add(_balanceAave()) + .add(_balance()); + } + + function calcPoolValueInToken() public view returns (uint) { + return balanceCompoundInToken() + .add(balanceFulcrumInToken()) + .add(balanceDydx()) + .add(balanceAave()) + .add(balance()); + } + + function getPricePerFullShare() public view returns (uint) { + uint _pool = calcPoolValueInToken(); + return _pool.mul(1e18).div(_totalSupply); + } + + // Redeem any invested tokens from the pool + function redeem(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check ETH balance + uint256 b = IERC20(token).balanceOf(address(this)); + Lender newProvider = provider; + if (b < r) { + newProvider = recommend(); + if (newProvider != provider) { + _withdrawAll(); + } else { + _withdrawSome(r.sub(b)); + } + } + + IERC20(token).safeTransfer(msg.sender, r); + + if (newProvider != provider) { + _rebalance(newProvider); + } + pool = calcPoolValueInToken(); + } +} \ No newline at end of file diff --git a/contracts/test/yearn/yUSDT.deployed.sol b/contracts/test/yearn/yUSDT.deployed.sol new file mode 100644 index 0000000..4c47fc3 --- /dev/null +++ b/contracts/test/yearn/yUSDT.deployed.sol @@ -0,0 +1,465 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; +//import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol"; + +import "./ERC20mod.sol"; +import "./Interfaces.sol"; +import "./DyDxStructs.sol"; + +interface IIEarnManager { + function recommend(address _token) external view returns ( + string memory choice, + uint256 capr, + uint256 iapr, + uint256 aapr, + uint256 dapr + ); +} + +contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + uint256 public pool; + address public token; + address public compound; + address public fulcrum; + address public aave; + address public aaveToken; + address public dydx; + uint256 public dToken; + address public apr; + + enum Lender { + NONE, + DYDX, + COMPOUND, + AAVE, + FULCRUM + } + + Lender public provider = Lender.NONE; + + function initialize() public initializer { + ERC20Detailed.initialize("iearn USDT", "yUSDT", 6); + token = address(0x24aDb40D9230BDdaF3b5b5f3428eA0B69a0aCaD7 ); + apr = address(0x9737E863fB48420928093072dF061542322C01e3); + dydx = address(0x3D248f7d805826F38D5D2000657176B9a2584455 ); + aave = address(0xEeA2F67b2c2f3a44bAC85a87f9d87265d2Ee7208 ); + fulcrum = address(0xeE12Cf70e0C2d7701F13BaA2Dc2b4711612dA698); + aaveToken = address(0xaa00a7D9CE6FF22bd1Cb02CdcBc4b8D6E4CEd737); + compound = address(0x2255e6309e0e8BfA8237fca3B50518607FD7B1F2); + dToken = 3; + approveToken(); + } + + // Quick swap low gas method for pool swaps + function deposit(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = _calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = _calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + // No rebalance implementation for lower fees and faster swaps + function withdraw(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = _calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check balance + uint256 b = IERC20(token).balanceOf(address(this)); + if (b < r) { + _withdrawSome(r.sub(b)); + } + + IERC20(token).transfer(msg.sender, r); + pool = _calcPoolValueInToken(); + } + + function() external payable { + + } + + function recommend() public view returns (Lender) { + (,uint256 capr,uint256 iapr,uint256 aapr,uint256 dapr) = IIEarnManager(apr).recommend(token); + uint256 max = 0; + if (capr > max) { + max = capr; + } + if (iapr > max) { + max = iapr; + } + if (aapr > max) { + max = aapr; + } + if (dapr > max) { + max = dapr; + } + + Lender newProvider = Lender.NONE; + if (max == capr) { + newProvider = Lender.COMPOUND; + } else if (max == iapr) { + newProvider = Lender.FULCRUM; + } else if (max == aapr) { + newProvider = Lender.AAVE; + } else if (max == dapr) { + newProvider = Lender.DYDX; + } + return newProvider; + } + + function supplyDydx(uint256 amount) public returns(uint) { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(true, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Deposit; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function _withdrawDydx(uint256 amount) internal { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(false, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Withdraw; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function getAave() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPool(); + } + function getAaveCore() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPoolCore(); + } + + function approveToken() public { + IERC20(token).safeApprove(compound, uint(-1)); //also add to constructor + IERC20(token).safeApprove(dydx, uint(-1)); + IERC20(token).safeApprove(getAaveCore(), uint(-1)); + IERC20(token).safeApprove(fulcrum, uint(-1)); + } + + function balance() public view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + function balanceDydx() public view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function balanceCompound() public view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function balanceCompoundInToken() public view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function balanceFulcrumInToken() public view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function balanceFulcrum() public view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function balanceAave() public view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function _balance() internal view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + function _balanceDydx() internal view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function _balanceCompound() internal view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function _balanceCompoundInToken() internal view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function _balanceFulcrumInToken() internal view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function _balanceFulcrum() internal view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function _balanceAave() internal view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function _withdrawAll() internal { + uint256 amount = _balanceCompound(); + if (amount > 0) { + _withdrawCompound(amount); + } + amount = _balanceDydx(); + if (amount > 0) { + _withdrawDydx(amount); + } + amount = _balanceFulcrum(); + if (amount > 0) { + _withdrawFulcrum(amount); + } + amount = _balanceAave(); + if (amount > 0) { + _withdrawAave(amount); + } + } + + function _withdrawSomeCompound(uint256 _amount) internal { + uint256 b = balanceCompound(); + uint256 bT = balanceCompoundInToken(); + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawCompound(amount); + } + + // 1999999614570950845 + function _withdrawSomeFulcrum(uint256 _amount) internal { + // Balance of fulcrum tokens, 1 iDAI = 1.00x DAI + uint256 b = balanceFulcrum(); // 1970469086655766652 + // Balance of token in fulcrum + uint256 bT = balanceFulcrumInToken(); // 2000000803224344406 + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawFulcrum(amount); + } + + function _withdrawSome(uint256 _amount) internal { + if (provider == Lender.COMPOUND) { + _withdrawSomeCompound(_amount); + } + if (provider == Lender.AAVE) { + require(balanceAave() >= _amount, "insufficient funds"); + _withdrawAave(_amount); + } + if (provider == Lender.DYDX) { + require(balanceDydx() >= _amount, "insufficient funds"); + _withdrawDydx(_amount); + } + if (provider == Lender.FULCRUM) { + _withdrawSomeFulcrum(_amount); + } + } + + function rebalance() public { + Lender newProvider = recommend(); + + if (newProvider != provider) { + _withdrawAll(); + } + + if (balance() > 0) { + if (newProvider == Lender.DYDX) { + supplyDydx(balance()); + } else if (newProvider == Lender.FULCRUM) { + supplyFulcrum(balance()); + } else if (newProvider == Lender.COMPOUND) { + supplyCompound(balance()); + } else if (newProvider == Lender.AAVE) { + supplyAave(balance()); + } + } + + provider = newProvider; + } + + // Internal only rebalance for better gas in redeem + function _rebalance(Lender newProvider) internal { + if (_balance() > 0) { + if (newProvider == Lender.DYDX) { + supplyDydx(_balance()); + } else if (newProvider == Lender.FULCRUM) { + supplyFulcrum(_balance()); + } else if (newProvider == Lender.COMPOUND) { + supplyCompound(_balance()); + } else if (newProvider == Lender.AAVE) { + supplyAave(_balance()); + } + } + provider = newProvider; + } + + function supplyAave(uint amount) public { + Aave(getAave()).deposit(token, amount, 0); + } + function supplyFulcrum(uint amount) public { + require(Fulcrum(fulcrum).mint(address(this), amount) > 0, "FULCRUM: supply failed"); + } + function supplyCompound(uint amount) public { + require(Compound(compound).mint(amount) == 0, "COMPOUND: supply failed"); + } + function _withdrawAave(uint amount) internal { + AToken(aaveToken).redeem(amount); + } + function _withdrawFulcrum(uint amount) internal { + require(Fulcrum(fulcrum).burn(address(this), amount) > 0, "FULCRUM: withdraw failed"); + } + function _withdrawCompound(uint amount) internal { + require(Compound(compound).redeem(amount) == 0, "COMPOUND: withdraw failed"); + } + + function invest(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + rebalance(); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + function _calcPoolValueInToken() internal view returns (uint) { + return _balanceCompoundInToken() + .add(_balanceFulcrumInToken()) + .add(_balanceDydx()) + .add(_balanceAave()) + .add(_balance()); + } + + function calcPoolValueInToken() public view returns (uint) { + return balanceCompoundInToken() + .add(balanceFulcrumInToken()) + .add(balanceDydx()) + .add(balanceAave()) + .add(balance()); + } + + function getPricePerFullShare() public view returns (uint) { + uint _pool = calcPoolValueInToken(); + return _pool.mul(1e18).div(_totalSupply); + } + + // Redeem any invested tokens from the pool + function redeem(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check ETH balance + uint256 b = IERC20(token).balanceOf(address(this)); + Lender newProvider = provider; + if (b < r) { + newProvider = recommend(); + if (newProvider != provider) { + _withdrawAll(); + } else { + _withdrawSome(r.sub(b)); + } + } + + IERC20(token).safeTransfer(msg.sender, r); + + if (newProvider != provider) { + _rebalance(newProvider); + } + pool = calcPoolValueInToken(); + } +} \ No newline at end of file