From dc0230e82af39668edee33a4e6bf9c7fc657e9aa Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Fri, 4 Dec 2020 10:56:30 +0300 Subject: [PATCH 01/10] DD-1321 Stubs for Fulcrum, AAVE and basic stub for DyDx, stub for iEarnManager --- contracts/test/yearn/AaveStub.sol | 57 ++ contracts/test/yearn/DyDxStub.sol | 66 ++ contracts/test/yearn/FulcrumStub.sol | 37 ++ contracts/test/yearn/IEarnAPRWithPool.sol | 697 +++++++++++++++++++ contracts/test/yearn/yDAI.deployed.sol | 773 ++++++++++++++++++++++ 5 files changed, 1630 insertions(+) create mode 100644 contracts/test/yearn/AaveStub.sol create mode 100644 contracts/test/yearn/DyDxStub.sol create mode 100644 contracts/test/yearn/FulcrumStub.sol create mode 100644 contracts/test/yearn/IEarnAPRWithPool.sol create mode 100644 contracts/test/yearn/yDAI.deployed.sol diff --git a/contracts/test/yearn/AaveStub.sol b/contracts/test/yearn/AaveStub.sol new file mode 100644 index 0000000..ce53d01 --- /dev/null +++ b/contracts/test/yearn/AaveStub.sol @@ -0,0 +1,57 @@ +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"; + + +contract ATokenStub is Base, ERC20, ERC20Detailed { + 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 { + 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); + } +} \ 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..9c18f32 --- /dev/null +++ b/contracts/test/yearn/DyDxStub.sol @@ -0,0 +1,66 @@ +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"; + +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 DyDxStub is Structs { + function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory) { + return Wei({ + sign: true, + value: 0 + }); + } + function operate(Info[] memory, ActionArgs[] memory) public { + } +} diff --git a/contracts/test/yearn/FulcrumStub.sol b/contracts/test/yearn/FulcrumStub.sol new file mode 100644 index 0000000..ad6e20c --- /dev/null +++ b/contracts/test/yearn/FulcrumStub.sol @@ -0,0 +1,37 @@ +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"; + +contract FulcrumStub is Base, ERC20, ERC20Detailed { + 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); + } + + function burn(address receiver, uint256 burnAmount) external returns (uint256 loanAmountPaid) { + _burnFrom(_msgSender(), burnAmount); + underlying.safeTransfer(receiver, burnAmount); + } + + function assetBalanceOf(address _owner) external view returns (uint256 balance) { + return balanceOf(_owner); + } +} \ No newline at end of file diff --git a/contracts/test/yearn/IEarnAPRWithPool.sol b/contracts/test/yearn/IEarnAPRWithPool.sol new file mode 100644 index 0000000..10fc987 --- /dev/null +++ b/contracts/test/yearn/IEarnAPRWithPool.sol @@ -0,0 +1,697 @@ +/** + *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 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 { +/* + 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/yDAI.deployed.sol b/contracts/test/yearn/yDAI.deployed.sol new file mode 100644 index 0000000..92d679c --- /dev/null +++ b/contracts/test/yearn/yDAI.deployed.sol @@ -0,0 +1,773 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +contract Context { + 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; + } +} + +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")); + } +} + +contract ERC20Detailed is IERC20 { + string private _name; + string private _symbol; + uint8 private _decimals; + + constructor (string memory name, string memory symbol, uint8 decimals) public { + _name = name; + _symbol = symbol; + _decimals = decimals; + } + function name() public view returns (string memory) { + return _name; + } + function symbol() public view returns (string memory) { + return _symbol; + } + function decimals() public view returns (uint8) { + return _decimals; + } +} + +contract ReentrancyGuard { + uint256 private _guardCounter; + + constructor () internal { + _guardCounter = 1; + } + + modifier nonReentrant() { + _guardCounter += 1; + uint256 localCounter = _guardCounter; + _; + require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call"); + } +} + +library SafeMath { + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + 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; + + return c; + } + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +library Address { + function isContract(address account) internal view returns (bool) { + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != 0x0 && codehash != accountHash); + } + function toPayable(address account) internal pure returns (address payable) { + return address(uint160(account)); + } + 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"); + } +} + +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + function safeApprove(IERC20 token, address spender, uint256 value) internal { + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + function callOptionalReturn(IERC20 token, bytes memory data) private { + require(address(token).isContract(), "SafeERC20: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, "SafeERC20: low-level call failed"); + + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +interface Compound { + function mint ( uint256 mintAmount ) external returns ( uint256 ); + function redeem(uint256 redeemTokens) external returns (uint256); + function exchangeRateStored() external view returns (uint); +} + +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); +} + +interface ILendingPoolAddressesProvider { + function getLendingPool() external view returns (address); +} + +interface Aave { + function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external; +} + +interface AToken { + function redeem(uint256 amount) external; +} + +interface IIEarnManager { + function recommend(address _token) external view returns ( + string memory choice, + uint256 capr, + uint256 iapr, + uint256 aapr, + uint256 dapr + ); +} + +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; +} + +interface LendingPoolAddressesProvider { + function getLendingPool() external view returns (address); + function getLendingPoolCore() external view returns (address); +} + +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; + + constructor () public ERC20Detailed("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 = 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 From 9b05d15198e258da61d10180e40f1d884ff48ea3 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Mon, 7 Dec 2020 10:38:54 +0300 Subject: [PATCH 02/10] DD-1321 improvement on dydx stub --- contracts/test/yearn/DyDxStub.sol | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/contracts/test/yearn/DyDxStub.sol b/contracts/test/yearn/DyDxStub.sol index 9c18f32..e4b0ce4 100644 --- a/contracts/test/yearn/DyDxStub.sol +++ b/contracts/test/yearn/DyDxStub.sol @@ -61,6 +61,27 @@ contract DyDxStub is Structs { value: 0 }); } - function operate(Info[] memory, ActionArgs[] memory) public { + 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; + if(arg.actionType == ActionType.Deposit) { + _deposit(beneficiary); + } else if(arg.actionType == ActionType.Deposit) { + _withdraw(beneficiary); + } else { + revert("Unsupported action"); + } + } + + function _deposit(address beneficiary) internal { + } + + function _withdraw(address beneficiary) internal { } } From d225cda185516abd95aa8a99b4d77600cf74cae7 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Tue, 8 Dec 2020 10:52:25 +0300 Subject: [PATCH 03/10] DD-1442 #comment cErc20 stub upgrade, Aave stub fix --- contracts/test/CErc20Stub.sol | 10 +++++++--- contracts/test/yearn/AaveStub.sol | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/test/CErc20Stub.sol b/contracts/test/CErc20Stub.sol index 5ff51e6..45321c5 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 powered to 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 index ce53d01..456e0e7 100644 --- a/contracts/test/yearn/AaveStub.sol +++ b/contracts/test/yearn/AaveStub.sol @@ -15,7 +15,7 @@ contract ATokenStub is Base, ERC20, ERC20Detailed { 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 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); From 3fcd2b44a85e9f8b75a0941f444f43203e2f5cf0 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Thu, 10 Dec 2020 10:50:47 +0300 Subject: [PATCH 04/10] DD-1438 #comment done --- .gitignore | 4 +++ contracts/test/yearn/DyDxStub.sol | 43 ++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 6 deletions(-) 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/yearn/DyDxStub.sol b/contracts/test/yearn/DyDxStub.sol index e4b0ce4..2a6cbfd 100644 --- a/contracts/test/yearn/DyDxStub.sol +++ b/contracts/test/yearn/DyDxStub.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.so 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"; contract Structs { struct Val { @@ -54,13 +55,36 @@ contract Structs { } } -contract DyDxStub is Structs { +contract DyDxStub is Base, Structs { + 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: 0 + 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++) { @@ -70,18 +94,25 @@ contract DyDxStub is Structs { 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(beneficiary); + _deposit(token, beneficiary, amount); } else if(arg.actionType == ActionType.Deposit) { - _withdraw(beneficiary); + _withdraw(token, beneficiary, amount); } else { revert("Unsupported action"); } } - function _deposit(address beneficiary) internal { + 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 beneficiary) internal { + function _withdraw(address token, address beneficiary, uint256 amount) internal { + balances[beneficiary].tokens[token] = balances[beneficiary].tokens[token].sub(amount); + IERC20(token).safeTransfer(beneficiary, amount); } } From da5c0a9f7b4694d4f085d019854522fa15ac1394 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Tue, 15 Dec 2020 16:09:35 +0300 Subject: [PATCH 05/10] DD-1439 ARP stub upgrade --- contracts/test/yearn/IEarnAPRWithPool.sol | 304 ++++++++++++---------- 1 file changed, 173 insertions(+), 131 deletions(-) diff --git a/contracts/test/yearn/IEarnAPRWithPool.sol b/contracts/test/yearn/IEarnAPRWithPool.sol index 10fc987..cfa2128 100644 --- a/contracts/test/yearn/IEarnAPRWithPool.sol +++ b/contracts/test/yearn/IEarnAPRWithPool.sol @@ -365,19 +365,19 @@ contract IUniswapAPR { 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); + 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); } @@ -386,8 +386,50 @@ interface IUniswapFactory { } interface IYToken { - function calcPoolValueInToken() external view returns (uint256); - function decimals() external view returns (uint256); + 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 0; + } + function getAaveAPR(address token) external view returns (uint256){ + return 0; + } + function getAaveAPRAdjusted(address token, uint256 _supply) external view returns (uint256){ + return 0; + } } @@ -497,176 +539,176 @@ contract IEarnAPRWithPool is Ownable { // Wrapper for legacy v1 token support function recommend(address _token) public view returns ( - string memory choice, - uint256 capr, - uint256 iapr, - uint256 aapr, - uint256 dapr + string memory choice, + uint256 capr, + uint256 iapr, + uint256 aapr, + uint256 dapr ) { - ( , capr, , iapr, , aapr, , dapr, , ) = getAPROptionsInc(_token); - return (choice, capr, iapr, aapr, 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 + 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); + 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 + uint256 _uniswap, + uint256 _compound, + uint256 _unicompound, + uint256 _fulcrum, + uint256 _unifulcrum, + uint256 _aave, + uint256 _uniaave, + uint256 _dydx, + uint256 _ddex, + uint256 _lendf ) { - return getAPROptionsAdjusted(_token, 0); + 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 _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]; + uint256 created = pools[_token]; + if (created > 0) { - _unicompound = IUniswapAPR(UNIAPR).calcUniswapAPR(addr, created); + _uniswap = IUniswapAPR(UNIAPR).calcUniswapAPR(_token, 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); + 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 = 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); + 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); } - } - - _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 - ); + + _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 + 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); + 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 + address token, + uint256 created ) public onlyOwner { pools[token] = created; } function addCToken( - address token, - address cToken + address token, + address cToken ) public onlyOwner { compound[token] = cToken; } function addIToken( - address token, - address iToken + address token, + address iToken ) public onlyOwner { fulcrum[token] = iToken; } function addAToken( - address token, - address aToken + address token, + address aToken ) public onlyOwner { aave[token] = aToken; } function addAUniToken( - address token, - address aToken + address token, + address aToken ) public onlyOwner { aaveUni[token] = aToken; } function addYToken( - address token, - address yToken + address token, + address yToken ) public onlyOwner { yTokens[token] = yToken; } function addDToken( - address token, - uint256 dToken + address token, + uint256 dToken ) public onlyOwner { dydx[token] = dToken; } From e96561490ba96e79629f86989f66d7cff3bcbb5c Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 16 Dec 2020 07:50:38 +0300 Subject: [PATCH 06/10] DD-1442 Fix comment --- contracts/test/CErc20Stub.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/CErc20Stub.sol b/contracts/test/CErc20Stub.sol index 45321c5..be1b515 100644 --- a/contracts/test/CErc20Stub.sol +++ b/contracts/test/CErc20Stub.sol @@ -11,7 +11,7 @@ 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_BASE = 20000000; // Same as real cTokens, will be powered to 1e + 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; From 361cae65b3c5a4553568ddabfe4228ee4c5f00bf Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 16 Dec 2020 07:52:22 +0300 Subject: [PATCH 07/10] DD-1436 Fix warning --- contracts/test/yearn/FulcrumStub.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/test/yearn/FulcrumStub.sol b/contracts/test/yearn/FulcrumStub.sol index ad6e20c..a9cffdb 100644 --- a/contracts/test/yearn/FulcrumStub.sol +++ b/contracts/test/yearn/FulcrumStub.sol @@ -24,14 +24,16 @@ contract FulcrumStub is Base, ERC20, ERC20Detailed { 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); } -} \ No newline at end of file +} From e850a17c8f0fa8dbe3b61dbf5ebbd38fd0ff2703 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 16 Dec 2020 08:33:49 +0300 Subject: [PATCH 08/10] DD-1440 extract interfaces to separate file to enable multiple yTokens --- contracts/test/yearn/AaveStub.sol | 14 +- contracts/test/yearn/DyDxStub.sol | 49 +--- contracts/test/yearn/DydxStructs.sol | 54 ++++ contracts/test/yearn/ERC20mod.sol | 79 +++++ contracts/test/yearn/FulcrumStub.sol | 4 +- contracts/test/yearn/IEarnAPRWithPool.sol | 2 +- contracts/test/yearn/Interfaces.sol | 27 ++ contracts/test/yearn/yDAI.deployed.sol | 336 +--------------------- 8 files changed, 193 insertions(+), 372 deletions(-) create mode 100644 contracts/test/yearn/DydxStructs.sol create mode 100644 contracts/test/yearn/ERC20mod.sol create mode 100644 contracts/test/yearn/Interfaces.sol diff --git a/contracts/test/yearn/AaveStub.sol b/contracts/test/yearn/AaveStub.sol index 456e0e7..add8fb5 100644 --- a/contracts/test/yearn/AaveStub.sol +++ b/contracts/test/yearn/AaveStub.sol @@ -6,8 +6,9 @@ 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 { +contract ATokenStub is Base, ERC20, ERC20Detailed, AToken { using SafeERC20 for IERC20; IERC20 public underlying; @@ -32,7 +33,7 @@ contract ATokenStub is Base, ERC20, ERC20Detailed { } -contract AaveStub is Base { +contract AaveStub is Base, LendingPoolAddressesProvider, Aave { using SafeERC20 for IERC20; mapping(address=>address) public tokens; @@ -54,4 +55,13 @@ contract AaveStub is Base { 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 index 2a6cbfd..27aedb8 100644 --- a/contracts/test/yearn/DyDxStub.sol +++ b/contracts/test/yearn/DyDxStub.sol @@ -8,54 +8,11 @@ import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Deta import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; -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; - } -} +import "./Interfaces.sol"; +import "./DyDxStructs.sol"; -contract DyDxStub is Base, Structs { +contract DyDxStub is Base, DyDx { using SafeMath for uint256; using SafeERC20 for IERC20; 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 index a9cffdb..aa87da1 100644 --- a/contracts/test/yearn/FulcrumStub.sol +++ b/contracts/test/yearn/FulcrumStub.sol @@ -6,7 +6,9 @@ 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"; -contract FulcrumStub is Base, ERC20, ERC20Detailed { +import "./Interfaces.sol"; + +contract FulcrumStub is Base, ERC20, ERC20Detailed, Fulcrum { using SafeERC20 for IERC20; IERC20 public underlying; diff --git a/contracts/test/yearn/IEarnAPRWithPool.sol b/contracts/test/yearn/IEarnAPRWithPool.sol index cfa2128..cd75b47 100644 --- a/contracts/test/yearn/IEarnAPRWithPool.sol +++ b/contracts/test/yearn/IEarnAPRWithPool.sol @@ -390,7 +390,7 @@ interface IYToken { function decimals() external view returns (uint256); } -contract APRWithPoolOracleStub() { +contract APRWithPoolOracleStub { function getDDEXAPR(address token) external view returns (uint256) { return 0; } 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/yDAI.deployed.sol b/contracts/test/yearn/yDAI.deployed.sol index 92d679c..8610cd1 100644 --- a/contracts/test/yearn/yDAI.deployed.sol +++ b/contracts/test/yearn/yDAI.deployed.sol @@ -1,270 +1,18 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; -interface IERC20 { - function totalSupply() external view returns (uint256); - function balanceOf(address account) external view returns (uint256); - function transfer(address recipient, uint256 amount) external returns (bool); - function allowance(address owner, address spender) external view returns (uint256); - function approve(address spender, uint256 amount) external returns (bool); - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval(address indexed owner, address indexed spender, uint256 value); -} - -contract Context { - 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; - } -} - -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")); - } -} - -contract ERC20Detailed is IERC20 { - string private _name; - string private _symbol; - uint8 private _decimals; - - constructor (string memory name, string memory symbol, uint8 decimals) public { - _name = name; - _symbol = symbol; - _decimals = decimals; - } - function name() public view returns (string memory) { - return _name; - } - function symbol() public view returns (string memory) { - return _symbol; - } - function decimals() public view returns (uint8) { - return _decimals; - } -} - -contract ReentrancyGuard { - uint256 private _guardCounter; - - constructor () internal { - _guardCounter = 1; - } - - modifier nonReentrant() { - _guardCounter += 1; - uint256 localCounter = _guardCounter; - _; - require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call"); - } -} - -library SafeMath { - function add(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a, "SafeMath: addition overflow"); - - return c; - } - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - return sub(a, b, "SafeMath: subtraction overflow"); - } - function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { - require(b <= a, errorMessage); - uint256 c = a - b; - - return c; - } - function mul(uint256 a, uint256 b) internal pure returns (uint256) { - if (a == 0) { - return 0; - } - - uint256 c = a * b; - require(c / a == b, "SafeMath: multiplication overflow"); - - return c; - } - function div(uint256 a, uint256 b) internal pure returns (uint256) { - return div(a, b, "SafeMath: division by zero"); - } - 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; - - return c; - } - function mod(uint256 a, uint256 b) internal pure returns (uint256) { - return mod(a, b, "SafeMath: modulo by zero"); - } - function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { - require(b != 0, errorMessage); - return a % b; - } -} - -library Address { - function isContract(address account) internal view returns (bool) { - bytes32 codehash; - bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; - // solhint-disable-next-line no-inline-assembly - assembly { codehash := extcodehash(account) } - return (codehash != 0x0 && codehash != accountHash); - } - function toPayable(address account) internal pure returns (address payable) { - return address(uint160(account)); - } - 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"); - } -} - -library SafeERC20 { - using SafeMath for uint256; - using Address for address; - - function safeTransfer(IERC20 token, address to, uint256 value) internal { - callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); - } - - function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { - callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); - } - - function safeApprove(IERC20 token, address spender, uint256 value) internal { - require((value == 0) || (token.allowance(address(this), spender) == 0), - "SafeERC20: approve from non-zero to non-zero allowance" - ); - callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); - } - - function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 newAllowance = token.allowance(address(this), spender).add(value); - callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); - } - - function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); - callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); - } - function callOptionalReturn(IERC20 token, bytes memory data) private { - require(address(token).isContract(), "SafeERC20: call to non-contract"); - - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory returndata) = address(token).call(data); - require(success, "SafeERC20: low-level call failed"); - - if (returndata.length > 0) { // Return data is optional - // solhint-disable-next-line max-line-length - require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); - } - } -} - -interface Compound { - function mint ( uint256 mintAmount ) external returns ( uint256 ); - function redeem(uint256 redeemTokens) external returns (uint256); - function exchangeRateStored() external view returns (uint); -} - -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); -} - -interface ILendingPoolAddressesProvider { - function getLendingPool() external view returns (address); -} - -interface Aave { - function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external; -} - -interface AToken { - function redeem(uint256 amount) external; -} +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 ( @@ -276,63 +24,6 @@ interface IIEarnManager { ); } -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; -} - -interface LendingPoolAddressesProvider { - function getLendingPool() external view returns (address); - function getLendingPoolCore() external view returns (address); -} - contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs { using SafeERC20 for IERC20; using Address for address; @@ -358,7 +49,8 @@ contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs { Lender public provider = Lender.NONE; - constructor () public ERC20Detailed("iearn DAI", "yDAI", 18) { + function initialize() public initializer { + ERC20Detailed.initialize("iearn DAI", "yDAI", 18); token = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); apr = address(0xdD6d648C991f7d47454354f4Ef326b04025a48A8); dydx = address(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); From 375befe867337a65479f7640c1d0b558227fd071 Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 16 Dec 2020 08:45:45 +0300 Subject: [PATCH 09/10] DD-1439 Fix compile error --- contracts/test/yearn/IEarnAPRWithPool.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/test/yearn/IEarnAPRWithPool.sol b/contracts/test/yearn/IEarnAPRWithPool.sol index cd75b47..4cdd464 100644 --- a/contracts/test/yearn/IEarnAPRWithPool.sol +++ b/contracts/test/yearn/IEarnAPRWithPool.sol @@ -422,7 +422,7 @@ contract APRWithPoolOracleStub { return 0; } function getAaveCore() external view returns (address){ - return 0; + return address(0); } function getAaveAPR(address token) external view returns (uint256){ return 0; @@ -451,6 +451,7 @@ contract IEarnAPRWithPool is Ownable { address public APR; constructor() public { + APR = address(new APRWithPoolOracleStub()); /* UNI = address(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95); UNIROI = address(0xD04cA0Ae1cd8085438FDd8c22A76246F315c2687); From 8f4eeaf77b75619b327fd941f4d5c2d30689ed5d Mon Sep 17 00:00:00 2001 From: Pavel Rubin Date: Wed, 16 Dec 2020 09:54:32 +0300 Subject: [PATCH 10/10] DD-1440 y-tokens with addresses --- contracts/test/yearn/yBUSD.deployed.sol | 465 ++++++++++++++++++++++++ contracts/test/yearn/yDAI.deployed.sol | 2 +- contracts/test/yearn/yTUSD.deployed.sol | 465 ++++++++++++++++++++++++ contracts/test/yearn/yUSDC.deployed.sol | 465 ++++++++++++++++++++++++ contracts/test/yearn/yUSDT.deployed.sol | 465 ++++++++++++++++++++++++ 5 files changed, 1861 insertions(+), 1 deletion(-) create mode 100644 contracts/test/yearn/yBUSD.deployed.sol create mode 100644 contracts/test/yearn/yTUSD.deployed.sol create mode 100644 contracts/test/yearn/yUSDC.deployed.sol create mode 100644 contracts/test/yearn/yUSDT.deployed.sol 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 index 8610cd1..f845351 100644 --- a/contracts/test/yearn/yDAI.deployed.sol +++ b/contracts/test/yearn/yDAI.deployed.sol @@ -58,7 +58,7 @@ contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs { fulcrum = address(0x493C57C4763932315A328269E1ADaD09653B9081); aaveToken = address(0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d); compound = address(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); - dToken = 3; + dToken = 1; approveToken(); } diff --git a/contracts/test/yearn/yTUSD.deployed.sol b/contracts/test/yearn/yTUSD.deployed.sol new file mode 100644 index 0000000..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