diff --git a/.gitignore b/.gitignore index d959af7..b82d395 100644 --- a/.gitignore +++ b/.gitignore @@ -31,9 +31,13 @@ types/* # Environment .env + # coverage/profiler report coverage coverage.json +#VSCode project +.vscode + # MacOS junk .DS_Store \ No newline at end of file diff --git a/contracts/test/CErc20Stub.sol b/contracts/test/CErc20Stub.sol index 5ff51e6..be1b515 100644 --- a/contracts/test/CErc20Stub.sol +++ b/contracts/test/CErc20Stub.sol @@ -11,16 +11,20 @@ contract CErc20Stub is Base, ICErc20, ERC20, ERC20Detailed { uint256 public constant EXP_SCALE = 1e18; //Exponential scale (see Compound Exponential) uint256 public constant INTEREST_RATE = 10 * EXP_SCALE / 100; // Annual interest 10% - uint256 public constant INITIAL_RATE = 200000000000000000000000000; // Same as real cDAI + uint256 public constant INITIAL_RATE_BASE = 20000000; // Same as real cTokens, will be multiblied by 1e uint256 public constant ANNUAL_SECONDS = 365*24*60*60+(24*60*60/4); // Seconds in a year + 1/4 day to compensate leap years uint256 private constant NO_ERROR = 0; FreeERC20 underlying; + uint256 initialRate; uint256 created; function initialize(address _underlying) public initializer { Base.initialize(); - ERC20Detailed.initialize("Compound Dai", "cDAI", 8); + string memory name = string(abi.encodePacked("Compound ", ERC20Detailed(_underlying).symbol())); + string memory symbol = string(abi.encodePacked("c", ERC20Detailed(_underlying).symbol())); + initialRate = INITIAL_RATE_BASE * (10 ** uint256(ERC20Detailed(_underlying).decimals())); + ERC20Detailed.initialize(name, symbol, 8); underlying = FreeERC20(_underlying); created = now; } @@ -77,6 +81,6 @@ contract CErc20Stub is Base, ICErc20, ERC20, ERC20Detailed { function _exchangeRate() internal view returns (uint256) { uint256 sec = now.sub(created); - return INITIAL_RATE.add(INITIAL_RATE.mul(INTEREST_RATE).mul(sec).div(ANNUAL_SECONDS).div(EXP_SCALE)); + return initialRate.add(initialRate.mul(INTEREST_RATE).mul(sec).div(ANNUAL_SECONDS).div(EXP_SCALE)); } } \ No newline at end of file diff --git a/contracts/test/yearn/AaveStub.sol b/contracts/test/yearn/AaveStub.sol new file mode 100644 index 0000000..add8fb5 --- /dev/null +++ b/contracts/test/yearn/AaveStub.sol @@ -0,0 +1,67 @@ +pragma solidity ^0.5.0; + +import "../../common/Base.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 "./Interfaces.sol"; + +contract ATokenStub is Base, ERC20, ERC20Detailed, AToken { + using SafeERC20 for IERC20; + + IERC20 public underlying; + + function initialize(address _underlying) public initializer { + Base.initialize(); + underlying = IERC20(_underlying); + string memory name = string(abi.encodePacked("Aave Interest bearing ", ERC20Detailed(_underlying).symbol())); + string memory symbol = string(abi.encodePacked("a", ERC20Detailed(_underlying).symbol())); + uint8 decimals = ERC20Detailed(_underlying).decimals(); + ERC20Detailed.initialize(name, symbol, decimals); + } + + function ownerMint(address receiver, uint256 amount) external onlyOwner { + _mint(receiver, amount); + } + + function redeem(uint256 amount) external { + _burnFrom(_msgSender(), amount); + underlying.safeTransfer(_msgSender(), amount); + } + +} + +contract AaveStub is Base, LendingPoolAddressesProvider, Aave { + using SafeERC20 for IERC20; + + mapping(address=>address) public tokens; + + function initialize() public initializer { + Base.initialize(); + } + + function createAToken(address _underlying) external onlyOwner { + ATokenStub aToken = new ATokenStub(); + aToken.initialize(_underlying); + tokens[_underlying] = address(aToken); + } + + + function deposit(address _reserve, uint256 _amount, uint16 /*_referralCode*/) external { + address aToken = tokens[_reserve]; + require(aToken != address(0), "Reserve token not supported"); + IERC20(_reserve).safeTransferFrom(_msgSender(), aToken, _amount); + ATokenStub(aToken).ownerMint(_msgSender(), _amount); + } + + function getLendingPool() external view returns (address) { + return address(this); + } + + function getLendingPoolCore() external view returns (address) { + return address(this); + } + +} \ No newline at end of file diff --git a/contracts/test/yearn/DyDxStub.sol b/contracts/test/yearn/DyDxStub.sol new file mode 100644 index 0000000..27aedb8 --- /dev/null +++ b/contracts/test/yearn/DyDxStub.sol @@ -0,0 +1,75 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +import "../../common/Base.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 "./Interfaces.sol"; +import "./DyDxStructs.sol"; + +contract DyDxStub is Base, DyDx { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + event MarketAdded(address token, uint256 market); + + struct AccountBalances { + mapping(address=>uint256) tokens; + } + + uint256 numMarkets; + mapping(uint256 => address) public markets; + mapping(address=>AccountBalances) balances; + + function addMarket(address token) external onlyOwner { + numMarkets += 1; + markets[numMarkets] = token; + emit MarketAdded(token, numMarkets); + } + + function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory) { + address beneficiary = account.owner; + address token = markets[marketId]; + require(token != address(0), "Market not found"); + return Wei({ + sign: true, + value: balances[beneficiary].tokens[token] + }); + } + + function operate(Info[] memory accounts, ActionArgs[] memory args) public { + require(accounts.length == args.length, "Array size mismatch"); + for(uint256 i=0; i< accounts.length; i++) { + _operate(accounts[i], args[i]); + } + } + + function _operate(Info memory account, ActionArgs memory arg) internal { + address beneficiary = account.owner; + address token = markets[arg.primaryMarketId]; + require(token != address(0), "Market not found"); + uint256 amount = arg.amount.value; + if(arg.actionType == ActionType.Deposit) { + _deposit(token, beneficiary, amount); + } else if(arg.actionType == ActionType.Deposit) { + _withdraw(token, beneficiary, amount); + } else { + revert("Unsupported action"); + } + } + + function _deposit(address token, address beneficiary, uint256 amount) internal { + IERC20(token).safeTransferFrom(beneficiary, address(this), amount); + balances[beneficiary].tokens[token] = balances[beneficiary].tokens[token].add(amount); + } + + function _withdraw(address token, address beneficiary, uint256 amount) internal { + balances[beneficiary].tokens[token] = balances[beneficiary].tokens[token].sub(amount); + IERC20(token).safeTransfer(beneficiary, amount); + } +} diff --git a/contracts/test/yearn/DydxStructs.sol b/contracts/test/yearn/DydxStructs.sol new file mode 100644 index 0000000..b8d8fd3 --- /dev/null +++ b/contracts/test/yearn/DydxStructs.sol @@ -0,0 +1,54 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +contract Structs { + struct Val { + uint256 value; + } + + enum ActionType { + Deposit, // supply tokens + Withdraw // borrow tokens + } + + enum AssetDenomination { + Wei // the amount is denominated in wei + } + + enum AssetReference { + Delta // the amount is given as a delta from the current value + } + + struct AssetAmount { + bool sign; // true if positive + AssetDenomination denomination; + AssetReference ref; + uint256 value; + } + + struct ActionArgs { + ActionType actionType; + uint256 accountId; + AssetAmount amount; + uint256 primaryMarketId; + uint256 secondaryMarketId; + address otherAddress; + uint256 otherAccountId; + bytes data; + } + + struct Info { + address owner; // The address that owns the account + uint256 number; // A nonce that allows a single address to control many accounts + } + + struct Wei { + bool sign; // true if positive + uint256 value; + } +} + +contract DyDx is Structs { + function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory); + function operate(Info[] memory, ActionArgs[] memory) public; +} \ No newline at end of file diff --git a/contracts/test/yearn/ERC20mod.sol b/contracts/test/yearn/ERC20mod.sol new file mode 100644 index 0000000..cbfe57e --- /dev/null +++ b/contracts/test/yearn/ERC20mod.sol @@ -0,0 +1,79 @@ +pragma solidity ^0.5.0; + +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/math/SafeMath.sol"; + + +contract ERC20 is Context, IERC20 { + using SafeMath for uint256; + + mapping (address => uint256) _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 _totalSupply; + function totalSupply() public view returns (uint256) { + return _totalSupply; + } + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + function transfer(address recipient, uint256 amount) public returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + function allowance(address owner, address spender) public view returns (uint256) { + return _allowances[owner][spender]; + } + function approve(address spender, uint256 amount) public returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + function _transfer(address sender, address recipient, uint256 amount) internal { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + function _mint(address account, uint256 amount) internal { + require(account != address(0), "ERC20: mint to the zero address"); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + function _burn(address account, uint256 amount) internal { + require(account != address(0), "ERC20: burn from the zero address"); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + function _approve(address owner, address spender, uint256 amount) internal { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + function _burnFrom(address account, uint256 amount) internal { + _burn(account, amount); + _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance")); + } +} \ No newline at end of file diff --git a/contracts/test/yearn/FulcrumStub.sol b/contracts/test/yearn/FulcrumStub.sol new file mode 100644 index 0000000..aa87da1 --- /dev/null +++ b/contracts/test/yearn/FulcrumStub.sol @@ -0,0 +1,41 @@ +pragma solidity ^0.5.0; + +import "../../common/Base.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 "./Interfaces.sol"; + +contract FulcrumStub is Base, ERC20, ERC20Detailed, Fulcrum { + using SafeERC20 for IERC20; + + IERC20 public underlying; + + function initialize(address _underlying) public initializer { + Base.initialize(); + underlying = IERC20(_underlying); + string memory name = string(abi.encodePacked("Fulcrum ", ERC20Detailed(_underlying).symbol(), "iToken")); + string memory symbol = string(abi.encodePacked("i", ERC20Detailed(_underlying).symbol())); + uint8 decimals = ERC20Detailed(_underlying).decimals(); + ERC20Detailed.initialize(name, symbol, decimals); + } + + + function mint(address receiver, uint256 amount) external payable returns (uint256 mintAmount) { + underlying.safeTransferFrom(_msgSender(), address(this), amount); + _mint(receiver, amount); + return mintAmount; + } + + function burn(address receiver, uint256 burnAmount) external returns (uint256 loanAmountPaid) { + _burnFrom(_msgSender(), burnAmount); + underlying.safeTransfer(receiver, burnAmount); + return burnAmount; + } + + function assetBalanceOf(address _owner) external view returns (uint256 balance) { + return balanceOf(_owner); + } +} diff --git a/contracts/test/yearn/IEarnAPRWithPool.sol b/contracts/test/yearn/IEarnAPRWithPool.sol new file mode 100644 index 0000000..4cdd464 --- /dev/null +++ b/contracts/test/yearn/IEarnAPRWithPool.sol @@ -0,0 +1,740 @@ +/** + *Submitted for verification at Etherscan.io on 2020-02-06 +*/ + +// File: @openzeppelin\contracts\token\ERC20\IERC20.sol + +pragma solidity ^0.5.0; + +interface IERC20 { + + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function transfer(address recipient, uint256 amount) external returns (bool); +} + +// File: @openzeppelin\contracts\GSN\Context.sol + +pragma solidity ^0.5.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +contract Context { + // Empty internal constructor, to prevent people from mistakenly deploying + // an instance of this contract, which should be used via inheritance. + constructor () internal { } + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return msg.sender; + } + + function _msgData() internal view returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// File: @openzeppelin\contracts\ownership\Ownable.sol + +pragma solidity ^0.5.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () internal { + _owner = _msgSender(); + emit OwnershipTransferred(address(0), _owner); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Returns true if the caller is the current owner. + */ + function isOwner() public view returns (bool) { + return _msgSender() == _owner; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +// File: @openzeppelin\contracts\math\SafeMath.sol + +pragma solidity ^0.5.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + * + * _Available since v2.4.0._ + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +// File: @openzeppelin\contracts\utils\Address.sol + +pragma solidity ^0.5.5; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * This test is non-exhaustive, and there may be false-negatives: during the + * execution of a contract's constructor, its address will be reported as + * not containing a contract. + * + * IMPORTANT: It is unsafe to assume that an address for which this + * function returns false is an externally-owned account (EOA) and not a + * contract. + */ + function isContract(address account) internal view returns (bool) { + // This method relies in extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != 0x0 && codehash != accountHash); + } + + /** + * @dev Converts an `address` into `address payable`. Note that this is + * simply a type cast: the actual underlying value is not changed. + * + * _Available since v2.4.0._ + */ + function toPayable(address account) internal pure returns (address payable) { + return address(uint160(account)); + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + * + * _Available since v2.4.0._ + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-call-value + (bool success, ) = recipient.call.value(amount)(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } +} + +interface IUniswapROI { + function calcUniswapROI(address token) external view returns (uint256, uint256); +} + +contract IUniswapAPR { + function getBlocksPerYear() external view returns (uint256); + function calcUniswapAPRFromROI(uint256 roi, uint256 createdAt) external view returns (uint256); + function calcUniswapAPR(address token, uint256 createdAt) external view returns (uint256); +} + +interface APRWithPoolOracle { + + function getDDEXAPR(address token) external view returns (uint256); + function getDDEXAPRAdjusted(address token, uint256 _supply) external view returns (uint256); + function getLENDFAPR(address token) external view returns (uint256); + function getLENDFAPRAdjusted(address token, uint256 _supply) external view returns (uint256); + function getCompoundAPR(address token) external view returns (uint256); + function getCompoundAPRAdjusted(address token, uint256 _supply) external view returns (uint256); + function getFulcrumAPR(address token) external view returns(uint256); + function getFulcrumAPRAdjusted(address token, uint256 _supply) external view returns(uint256); + function getDyDxAPR(uint256 marketId) external view returns(uint256); + function getDyDxAPRAdjusted(uint256 marketId, uint256 _supply) external view returns(uint256); + function getAaveCore() external view returns (address); + function getAaveAPR(address token) external view returns (uint256); + function getAaveAPRAdjusted(address token, uint256 _supply) external view returns (uint256); + +} + +interface IUniswapFactory { + function getExchange(address token) external view returns (address exchange); +} + +interface IYToken { + function calcPoolValueInToken() external view returns (uint256); + function decimals() external view returns (uint256); +} + +contract APRWithPoolOracleStub { + function getDDEXAPR(address token) external view returns (uint256) { + return 0; + } + function getDDEXAPRAdjusted(address token, uint256 _supply) external view returns (uint256){ + return 0; + } + function getLENDFAPR(address token) external view returns (uint256){ + return 0; + } + function getLENDFAPRAdjusted(address token, uint256 _supply) external view returns (uint256){ + return 0; + } + function getCompoundAPR(address token) external view returns (uint256){ + return 10; + } + function getCompoundAPRAdjusted(address token, uint256 _supply) external view returns (uint256){ + return 10; + } + function getFulcrumAPR(address token) external view returns(uint256){ + return 0; + } + function getFulcrumAPRAdjusted(address token, uint256 _supply) external view returns(uint256){ + return 0; + } + function getDyDxAPR(uint256 marketId) external view returns(uint256){ + return 0; + } + function getDyDxAPRAdjusted(uint256 marketId, uint256 _supply) external view returns(uint256){ + return 0; + } + function getAaveCore() external view returns (address){ + return address(0); + } + function getAaveAPR(address token) external view returns (uint256){ + return 0; + } + function getAaveAPRAdjusted(address token, uint256 _supply) external view returns (uint256){ + return 0; + } +} + + +contract IEarnAPRWithPool is Ownable { + using SafeMath for uint; + using Address for address; + + mapping(address => uint256) public pools; + mapping(address => address) public compound; + mapping(address => address) public fulcrum; + mapping(address => address) public aave; + mapping(address => address) public aaveUni; + mapping(address => uint256) public dydx; + mapping(address => address) public yTokens; + + address public UNI; + address public UNIROI; + address public UNIAPR; + address public APR; + + constructor() public { + APR = address(new APRWithPoolOracleStub()); +/* + UNI = address(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95); + UNIROI = address(0xD04cA0Ae1cd8085438FDd8c22A76246F315c2687); + UNIAPR = address(0x4c70D89A4681b2151F56Dc2c3FD751aBb9CE3D95); + APR = address(0xeC3aDd301dcAC0e9B0B880FCf6F92BDfdc002BBc); + + addPool(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643, 9000629); + addPool(0xF5DCe57282A584D2746FaF1593d3121Fcac444dC, 7723867); + addPool(0x6B175474E89094C44Da98b954EedeAC495271d0F, 8939330); + addPool(0x0000000000085d4780B73119b644AE5ecd22b376, 7794100); + addPool(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 6783192); + addPool(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51, 8623684); + addPool(0x0D8775F648430679A709E98d2b0Cb6250d2887EF, 6660894); + addPool(0x514910771AF9Ca656af840dff83E8264EcF986CA, 6627987); + addPool(0xdd974D5C2e2928deA5F71b9825b8b646686BD200, 6627984); + addPool(0x1985365e9f78359a9B6AD760e32412f4a445E862, 6627994); + addPool(0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2, 6627956); + addPool(0xE41d2489571d322189246DaFA5ebDe1F4699F498, 6627972); + addPool(0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F, 8314762); + addPool(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 7004537); + + addCToken(0x6B175474E89094C44Da98b954EedeAC495271d0F, 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); // cDAI + addCToken(0x0D8775F648430679A709E98d2b0Cb6250d2887EF, 0x6C8c6b02E7b2BE14d4fA6022Dfd6d75921D90E4E); // cBAT + addCToken(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5); // cETH + addCToken(0x1985365e9f78359a9B6AD760e32412f4a445E862, 0x158079Ee67Fce2f58472A96584A73C7Ab9AC95c1); // cREP + addCToken(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0x39AA39c021dfbaE8faC545936693aC917d5E7563); // cUSDC + addCToken(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0xC11b1268C1A384e55C48c2391d8d480264A3A7F4); // cWBTC + addCToken(0xE41d2489571d322189246DaFA5ebDe1F4699F498, 0xB3319f5D18Bc0D84dD1b4825Dcde5d5f7266d407); // cZRX + + + addAToken(0x6B175474E89094C44Da98b954EedeAC495271d0F, 0x6B175474E89094C44Da98b954EedeAC495271d0F); // aDAI + addAToken(0x0000000000085d4780B73119b644AE5ecd22b376, 0x0000000000085d4780B73119b644AE5ecd22b376); // aTUSD + addAToken(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); // aUSDC + addAToken(0xdAC17F958D2ee523a2206206994597C13D831ec7, 0xdAC17F958D2ee523a2206206994597C13D831ec7); // aUSDT + addAToken(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51, 0x57Ab1ec28D129707052df4dF418D58a2D46d5f51); // aSUSD + addAToken(0x80fB784B7eD66730e8b1DBd9820aFD29931aab03, 0x80fB784B7eD66730e8b1DBd9820aFD29931aab03); // aLEND + addAToken(0x0D8775F648430679A709E98d2b0Cb6250d2887EF, 0x0D8775F648430679A709E98d2b0Cb6250d2887EF); // aBAT + addAToken(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); // aETH + addAToken(0x514910771AF9Ca656af840dff83E8264EcF986CA, 0x514910771AF9Ca656af840dff83E8264EcF986CA); // aLINK + addAToken(0xdd974D5C2e2928deA5F71b9825b8b646686BD200, 0xdd974D5C2e2928deA5F71b9825b8b646686BD200); // aKNC + addAToken(0x1985365e9f78359a9B6AD760e32412f4a445E862, 0x1985365e9f78359a9B6AD760e32412f4a445E862); // aREP + addAToken(0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2, 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2); // aMKR + addAToken(0x0F5D2fB29fb7d3CFeE444a200298f468908cC942, 0x0F5D2fB29fb7d3CFeE444a200298f468908cC942); // aMANA + addAToken(0xE41d2489571d322189246DaFA5ebDe1F4699F498, 0xE41d2489571d322189246DaFA5ebDe1F4699F498); // aZRX + addAToken(0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F, 0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F); // aSNX + addAToken(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); // aWBTC + + addAUniToken(0x6B175474E89094C44Da98b954EedeAC495271d0F, 0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d); // aDAI + addAUniToken(0x0000000000085d4780B73119b644AE5ecd22b376, 0x4DA9b813057D04BAef4e5800E36083717b4a0341); // aTUSD + addAUniToken(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0x9bA00D6856a4eDF4665BcA2C2309936572473B7E); // aUSDC + addAUniToken(0xdAC17F958D2ee523a2206206994597C13D831ec7, 0x71fc860F7D3A592A4a98740e39dB31d25db65ae8); // aUSDT + addAUniToken(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51, 0x625aE63000f46200499120B906716420bd059240); // aSUSD + addAUniToken(0x80fB784B7eD66730e8b1DBd9820aFD29931aab03, 0x7D2D3688Df45Ce7C552E19c27e007673da9204B8); // aLEND + addAUniToken(0x0D8775F648430679A709E98d2b0Cb6250d2887EF, 0xE1BA0FB44CCb0D11b80F92f4f8Ed94CA3fF51D00); // aBAT + addAUniToken(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, 0x3a3A65aAb0dd2A17E3F1947bA16138cd37d08c04); // aETH + addAUniToken(0x514910771AF9Ca656af840dff83E8264EcF986CA, 0xA64BD6C70Cb9051F6A9ba1F163Fdc07E0DfB5F84); // aLINK + addAUniToken(0xdd974D5C2e2928deA5F71b9825b8b646686BD200, 0x9D91BE44C06d373a8a226E1f3b146956083803eB); // aKNC + addAUniToken(0x1985365e9f78359a9B6AD760e32412f4a445E862, 0x71010A9D003445aC60C4e6A7017c1E89A477B438); // aREP + addAUniToken(0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2, 0x7deB5e830be29F91E298ba5FF1356BB7f8146998); // aMKR + addAUniToken(0x0F5D2fB29fb7d3CFeE444a200298f468908cC942, 0x6FCE4A401B6B80ACe52baAefE4421Bd188e76F6f); // aMANA + addAUniToken(0xE41d2489571d322189246DaFA5ebDe1F4699F498, 0x6Fb0855c404E09c47C3fBCA25f08d4E41f9F062f); // aZRX + addAUniToken(0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F, 0x328C4c80BC7aCa0834Db37e6600A6c49E12Da4DE); // aSNX + addAUniToken(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0xFC4B8ED459e00e5400be803A9BB3954234FD50e3); // aWBTC + + addIToken(0xE41d2489571d322189246DaFA5ebDe1F4699F498, 0xA7Eb2bc82df18013ecC2A6C533fc29446442EDEe); // iZRX + addIToken(0x1985365e9f78359a9B6AD760e32412f4a445E862, 0xBd56E9477Fc6997609Cf45F84795eFbDAC642Ff1); // iREP + addIToken(0xdd974D5C2e2928deA5F71b9825b8b646686BD200, 0x1cC9567EA2eB740824a45F8026cCF8e46973234D); // iKNC + addIToken(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0xBA9262578EFef8b3aFf7F60Cd629d6CC8859C8b5); // iWBTC + addIToken(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xF013406A0B1d544238083DF0B93ad0d2cBE0f65f); // iUSDC + addIToken(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, 0x77f973FCaF871459aa58cd81881Ce453759281bC); // iETH + addIToken(0x6B175474E89094C44Da98b954EedeAC495271d0F, 0x493C57C4763932315A328269E1ADaD09653B9081); // iDAI + addIToken(0x514910771AF9Ca656af840dff83E8264EcF986CA, 0x1D496da96caf6b518b133736beca85D5C4F9cBc5); // iLINK + addIToken(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51, 0x49f4592E641820e928F9919Ef4aBd92a719B4b49); // iSUSD + + addDToken(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, 0); // dETH + addDToken(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 2); // dUSDC + addDToken(0x6B175474E89094C44Da98b954EedeAC495271d0F, 3); // dDAI + + addYToken(0x6B175474E89094C44Da98b954EedeAC495271d0F, 0x9D25057e62939D3408406975aD75Ffe834DA4cDd); // yDAI + addYToken(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xa2609B2b43AC0F5EbE27deB944d2a399C201E3dA); // yUSDC + addYToken(0xdAC17F958D2ee523a2206206994597C13D831ec7, 0xa1787206d5b1bE0f432C4c4f96Dc4D1257A1Dd14); // yUSDT + addYToken(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51, 0x36324b8168f960A12a8fD01406C9C78143d41380); // ySUSD + addYToken(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x04EF8121aD039ff41d10029c91EA1694432514e9); // yWBTC +*/ + } + + // Wrapper for legacy v1 token support + function recommend(address _token) public view returns ( + string memory choice, + uint256 capr, + uint256 iapr, + uint256 aapr, + uint256 dapr + ) { + ( , capr, , iapr, , aapr, , dapr, , ) = getAPROptionsInc(_token); + return (choice, capr, iapr, aapr, dapr); + } + + function getAPROptionsInc(address _token) public view returns ( + uint256 _uniswap, + uint256 _compound, + uint256 _unicompound, + uint256 _fulcrum, + uint256 _unifulcrum, + uint256 _aave, + uint256 _uniaave, + uint256 _dydx, + uint256 _ddex, + uint256 _lendf + ) { + address yToken = yTokens[_token]; + uint256 _supply = 0; + if (yToken != address(0)) { + _supply = IYToken(yToken).calcPoolValueInToken(); + } + return getAPROptionsAdjusted(_token, _supply); + } + + function getAPROptions(address _token) public view returns ( + uint256 _uniswap, + uint256 _compound, + uint256 _unicompound, + uint256 _fulcrum, + uint256 _unifulcrum, + uint256 _aave, + uint256 _uniaave, + uint256 _dydx, + uint256 _ddex, + uint256 _lendf + ) { + return getAPROptionsAdjusted(_token, 0); + } + + function getAPROptionsAdjusted(address _token, uint256 _supply) public view returns ( + uint256 _uniswap, + uint256 _compound, + uint256 _unicompound, + uint256 _fulcrum, + uint256 _unifulcrum, + uint256 _aave, + uint256 _uniaave, + uint256 _dydx, + uint256 _ddex, + uint256 _lendf + ) { + uint256 created = pools[_token]; + + if (created > 0) { + _uniswap = IUniswapAPR(UNIAPR).calcUniswapAPR(_token, created); + } + address addr = compound[_token]; + if (addr != address(0)) { + _compound = APRWithPoolOracle(APR).getCompoundAPR(addr); + created = pools[addr]; + if (created > 0) { + _unicompound = IUniswapAPR(UNIAPR).calcUniswapAPR(addr, created); + } + } + addr = fulcrum[_token]; + if (addr != address(0)) { + _fulcrum = APRWithPoolOracle(APR).getFulcrumAPRAdjusted(addr, _supply); + created = pools[addr]; + if (created > 0) { + _unifulcrum = IUniswapAPR(UNIAPR).calcUniswapAPR(addr, created); + } + } + addr = aave[_token]; + if (addr != address(0)) { + _aave = APRWithPoolOracle(APR).getAaveAPRAdjusted(addr, _supply); + addr = aaveUni[_token]; + created = pools[addr]; + if (created > 0) { + _uniaave = IUniswapAPR(UNIAPR).calcUniswapAPR(addr, created); + } + } + + _dydx = dydx[_token]; + if (_dydx > 0 || _token == address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) { + _dydx = APRWithPoolOracle(APR).getDyDxAPRAdjusted(_dydx, _supply); + } + + _ddex = APRWithPoolOracle(APR).getDDEXAPRAdjusted(_token, _supply); + _lendf = APRWithPoolOracle(APR).getLENDFAPRAdjusted(_token, _supply); + + return ( + _uniswap, + _compound, + _unicompound, + _fulcrum, + _unifulcrum, + _aave, + _uniaave, + _dydx, + _ddex, + _lendf + ); + } + + function viewPool(address _token) public view returns ( + address token, + address unipool, + uint256 created, + string memory name, + string memory symbol + ) { + token = _token; + unipool = IUniswapFactory(UNI).getExchange(_token); + created = pools[_token]; + name = IERC20(_token).name(); + symbol = IERC20(_token).symbol(); + return (token, unipool, created, name, symbol); + } + + function addPool( + address token, + uint256 created + ) public onlyOwner { + pools[token] = created; + } + + function addCToken( + address token, + address cToken + ) public onlyOwner { + compound[token] = cToken; + } + + function addIToken( + address token, + address iToken + ) public onlyOwner { + fulcrum[token] = iToken; + } + + function addAToken( + address token, + address aToken + ) public onlyOwner { + aave[token] = aToken; + } + + function addAUniToken( + address token, + address aToken + ) public onlyOwner { + aaveUni[token] = aToken; + } + + function addYToken( + address token, + address yToken + ) public onlyOwner { + yTokens[token] = yToken; + } + + function addDToken( + address token, + uint256 dToken + ) public onlyOwner { + dydx[token] = dToken; + } + + function set_new_UNIROI(address _new_UNIROI) public onlyOwner { + UNIROI = _new_UNIROI; + } + function set_new_UNI(address _new_UNI) public onlyOwner { + UNI = _new_UNI; + } + function set_new_UNIAPR(address _new_UNIAPR) public onlyOwner { + UNIAPR = _new_UNIAPR; + } + function set_new_APR(address _new_APR) public onlyOwner { + APR = _new_APR; + } + + // incase of half-way error + function inCaseTokenGetsStuck(IERC20 _TokenAddress) onlyOwner public { + uint qty = _TokenAddress.balanceOf(address(this)); + _TokenAddress.transfer(msg.sender, qty); + } + // incase of half-way error + function inCaseETHGetsStuck() onlyOwner public{ + (bool result, ) = msg.sender.call.value(address(this).balance)(""); + require(result, "transfer of ETH failed"); + } +} \ No newline at end of file diff --git a/contracts/test/yearn/Interfaces.sol b/contracts/test/yearn/Interfaces.sol new file mode 100644 index 0000000..11730bf --- /dev/null +++ b/contracts/test/yearn/Interfaces.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.5.0; + +interface Compound { + function mint ( uint256 mintAmount ) external returns ( uint256 ); + function redeem(uint256 redeemTokens) external returns (uint256); + function exchangeRateStored() external view returns (uint); +} + +interface Aave { + function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external; +} + +interface AToken { + function redeem(uint256 amount) external; +} + +interface LendingPoolAddressesProvider { + function getLendingPool() external view returns (address); + function getLendingPoolCore() external view returns (address); +} + +interface Fulcrum { + function mint(address receiver, uint256 amount) external payable returns (uint256 mintAmount); + function burn(address receiver, uint256 burnAmount) external returns (uint256 loanAmountPaid); + function assetBalanceOf(address _owner) external view returns (uint256 balance); +} + diff --git a/contracts/test/yearn/yBUSD.deployed.sol b/contracts/test/yearn/yBUSD.deployed.sol new file mode 100644 index 0000000..0ae8902 --- /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 yBUSD 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 new file mode 100644 index 0000000..f845351 --- /dev/null +++ b/contracts/test/yearn/yDAI.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 DAI", "yDAI", 18); + token = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); + apr = address(0xdD6d648C991f7d47454354f4Ef326b04025a48A8); + dydx = address(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); + aave = address(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); + fulcrum = address(0x493C57C4763932315A328269E1ADaD09653B9081); + aaveToken = address(0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d); + compound = address(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); + dToken = 1; + 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/yTUSD.deployed.sol b/contracts/test/yearn/yTUSD.deployed.sol new file mode 100644 index 0000000..b149a41 --- /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 yTUSD 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..2eb1f93 --- /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 yUSDC 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..5297409 --- /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 yUSDT 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