diff --git a/contracts/Lens/CompoundLens.sol b/contracts/Lens/CompoundLens.sol new file mode 100644 index 000000000..6cbf03ca0 --- /dev/null +++ b/contracts/Lens/CompoundLens.sol @@ -0,0 +1,159 @@ +pragma solidity ^0.5.16; +pragma experimental ABIEncoderV2; + +import "../CErc20.sol"; +import "../CToken.sol"; +import "../Comptroller.sol"; +import "../EIP20Interface.sol"; + +contract CompoundLens { + struct CTokenMetadata { + address cToken; + uint exchangeRateCurrent; + uint supplyRatePerBlock; + uint borrowRatePerBlock; + uint reserveFactorMantissa; + uint totalBorrows; + uint totalReserves; + uint totalSupply; + uint totalCash; + bool isListed; + uint collateralFactorMantissa; + address underlyingAssetAddress; + uint cTokenDecimals; + uint underlyingDecimals; + } + + function cTokenMetadata(CToken cToken) public returns (CTokenMetadata memory) { + uint exchangeRateCurrent = cToken.exchangeRateCurrent(); + Comptroller comptroller = Comptroller(address(cToken.comptroller())); + (bool isListed, uint collateralFactorMantissa) = comptroller.markets(address(cToken)); + address underlyingAssetAddress; + uint underlyingDecimals; + + if (compareStrings(cToken.symbol(), "cETH")) { + underlyingAssetAddress = address(0); + underlyingDecimals = 18; + } else { + CErc20 cErc20 = CErc20(address(cToken)); + underlyingAssetAddress = cErc20.underlying(); + underlyingDecimals = EIP20Interface(cErc20.underlying()).decimals(); + } + + return CTokenMetadata({ + cToken: address(cToken), + exchangeRateCurrent: exchangeRateCurrent, + supplyRatePerBlock: cToken.supplyRatePerBlock(), + borrowRatePerBlock: cToken.borrowRatePerBlock(), + reserveFactorMantissa: cToken.reserveFactorMantissa(), + totalBorrows: cToken.totalBorrows(), + totalReserves: cToken.totalReserves(), + totalSupply: cToken.totalSupply(), + totalCash: cToken.getCash(), + isListed: isListed, + collateralFactorMantissa: collateralFactorMantissa, + underlyingAssetAddress: underlyingAssetAddress, + cTokenDecimals: cToken.decimals(), + underlyingDecimals: underlyingDecimals + }); + } + + function cTokenMetadataAll(CToken[] calldata cTokens) external returns (CTokenMetadata[] memory) { + uint cTokenCount = cTokens.length; + CTokenMetadata[] memory res = new CTokenMetadata[](cTokenCount); + for (uint i = 0; i < cTokenCount; i++) { + res[i] = cTokenMetadata(cTokens[i]); + } + return res; + } + + struct CTokenBalances { + address cToken; + uint balanceOf; + uint borrowBalanceCurrent; + uint balanceOfUnderlying; + uint tokenBalance; + uint tokenAllowance; + } + + function cTokenBalances(CToken cToken, address payable account) public returns (CTokenBalances memory) { + uint balanceOf = cToken.balanceOf(account); + uint borrowBalanceCurrent = cToken.borrowBalanceCurrent(account); + uint balanceOfUnderlying = cToken.balanceOfUnderlying(account); + uint tokenBalance; + uint tokenAllowance; + + if (compareStrings(cToken.symbol(), "cETH")) { + tokenBalance = account.balance; + tokenAllowance = account.balance; + } else { + CErc20 cErc20 = CErc20(address(cToken)); + EIP20Interface underlying = EIP20Interface(cErc20.underlying()); + tokenBalance = underlying.balanceOf(account); + tokenAllowance = underlying.allowance(account, address(cToken)); + } + + return CTokenBalances({ + cToken: address(cToken), + balanceOf: balanceOf, + borrowBalanceCurrent: borrowBalanceCurrent, + balanceOfUnderlying: balanceOfUnderlying, + tokenBalance: tokenBalance, + tokenAllowance: tokenAllowance + }); + } + + function cTokenBalancesAll(CToken[] calldata cTokens, address payable account) external returns (CTokenBalances[] memory) { + uint cTokenCount = cTokens.length; + CTokenBalances[] memory res = new CTokenBalances[](cTokenCount); + for (uint i = 0; i < cTokenCount; i++) { + res[i] = cTokenBalances(cTokens[i], account); + } + return res; + } + + struct CTokenUnderlyingPrice { + address cToken; + uint underlyingPrice; + } + + function cTokenUnderlyingPrice(CToken cToken) public returns (CTokenUnderlyingPrice memory) { + Comptroller comptroller = Comptroller(address(cToken.comptroller())); + PriceOracle priceOracle = comptroller.oracle(); + + return CTokenUnderlyingPrice({ + cToken: address(cToken), + underlyingPrice: priceOracle.getUnderlyingPrice(cToken) + }); + } + + function cTokenUnderlyingPriceAll(CToken[] calldata cTokens) external returns (CTokenUnderlyingPrice[] memory) { + uint cTokenCount = cTokens.length; + CTokenUnderlyingPrice[] memory res = new CTokenUnderlyingPrice[](cTokenCount); + for (uint i = 0; i < cTokenCount; i++) { + res[i] = cTokenUnderlyingPrice(cTokens[i]); + } + return res; + } + + struct AccountLimits { + CToken[] markets; + uint liquidity; + uint shortfall; + } + + function getAccountLimits(Comptroller comptroller, address account) public returns (AccountLimits memory) { + (uint errorCode, uint liquidity, uint shortfall) = comptroller.getAccountLiquidity(account); + require(errorCode == 0); + + return AccountLimits({ + markets: comptroller.getAssetsIn(account), + liquidity: liquidity, + shortfall: shortfall + }); + } + + function compareStrings(string memory a, string memory b) internal pure returns (bool) { + return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); + } +} diff --git a/networks/goerli.json b/networks/goerli.json index acbe928df..4392e426f 100644 --- a/networks/goerli.json +++ b/networks/goerli.json @@ -30,7 +30,8 @@ "REP": "0x183Faf58c4461972765f3F90c6272A4ecE66Bd96", "cZRX": "0xA253295eC2157B8b69C44b2cb35360016DAa25b1", "cWBTC": "0x6CE27497A64fFFb5517AA4aeE908b1E7EB63B9fF", - "USDC": "0xD87Ba7A50B2E7E660f678A895E4B72E7CB4CCd9C" + "USDC": "0xD87Ba7A50B2E7E660f678A895E4B72E7CB4CCd9C", + "CompoundLens": "0x05752E558f7141C4b098D9b9B212F4Ea5aff0A3c" }, "Blocks": { "ZRX": 1971943, diff --git a/networks/kovan.json b/networks/kovan.json index f0fdefd26..2d06cdfdb 100644 --- a/networks/kovan.json +++ b/networks/kovan.json @@ -29,7 +29,8 @@ "cZRX": "0xC014DC10A57aC78350C5fddB26Bb66f1Cb0960a0", "cWBTC": "0x3659728876EfB2780f498Ce829C5b076e496E0e3", "USDC": "0x75B0622Cec14130172EaE9Cf166B92E5C112FaFF", - "Base200bps_Slope222bps_Kink90_Jump10": "0xE057EF4be5c6C712e13E24d4408f5F257fEd4d3f" + "Base200bps_Slope222bps_Kink90_Jump10": "0xE057EF4be5c6C712e13E24d4408f5F257fEd4d3f", + "CompoundLens": "0x6F4853929e4C30Fcd357f36d8d1aa9b5fAC82185" }, "Blocks": { "ZRX": 14981231, diff --git a/networks/mainnet.json b/networks/mainnet.json index 173721d11..73f9cbca8 100644 --- a/networks/mainnet.json +++ b/networks/mainnet.json @@ -36,7 +36,8 @@ "REP": "0x1985365e9f78359a9B6AD760e32412f4a445E862", "cZRX": "0xB3319f5D18Bc0D84dD1b4825Dcde5d5f7266d407", "cWBTC": "0xC11b1268C1A384e55C48c2391d8d480264A3A7F4", - "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "CompoundLens": "0xBaaD2b744336D6E4E67Fae2e071C3005b8C17102" }, "Blocks": { "cUSDC": 7710760, diff --git a/networks/rinkeby.json b/networks/rinkeby.json index abd180637..4f7ae16d3 100644 --- a/networks/rinkeby.json +++ b/networks/rinkeby.json @@ -24,7 +24,8 @@ "REP": "0x6e894660985207feb7cf89Faf048998c71E8EE89", "cZRX": "0x52201ff1720134bBbBB2f6BC97Bf3715490EC19B", "cWBTC": "0x0014F450B8Ae7708593F4A46F8fa6E5D50620F96", - "USDC": "0x4DBCdF9B62e891a7cec5A2568C3F4FAF9E8Abe2b" + "USDC": "0x4DBCdF9B62e891a7cec5A2568C3F4FAF9E8Abe2b", + "CompoundLens": "0x0E01460f00C6B0b66A44D7cF1406Cb2150B718fC" }, "Blocks": { "cUSDC": 4319847, diff --git a/networks/ropsten.json b/networks/ropsten.json index 175ec17be..2b0a7131a 100644 --- a/networks/ropsten.json +++ b/networks/ropsten.json @@ -30,7 +30,8 @@ "cZRX": "0x3A728dD027AD6F76Cdea227d5Cf5ba7ce9390A3d", "cWBTC": "0x4D15eE7DE1f86248c986f5AE7dCE855b1c1A8806", "TBTC": "0x083f652051b9CdBf65735f98d83cc329725Aa957", - "USDC": "0x8a9447df1FB47209D36204e6D56767a33bf20f9f" + "USDC": "0x8a9447df1FB47209D36204e6D56767a33bf20f9f", + "CompoundLens": "0x1496C81e6DE4928bE5E4e9F83dA575A08B616Ac8" }, "Blocks": { "ZRX": 5970747, diff --git a/saddle.config.js b/saddle.config.js index a83f995de..00298e516 100644 --- a/saddle.config.js +++ b/saddle.config.js @@ -156,6 +156,32 @@ module.exports = { {unlocked: 0} ] }, + kovan: { + providers: [ + {env: "PROVIDER"}, + {file: "~/.ethereum/kovan-url"}, // Load from given file with contents as the URL (e.g. https://infura.io/api-key) + {http: "https://kovan-eth.compound.finance"} + ], + web3: { + gas: [ + {env: "GAS"}, + {default: "4600000"} + ], + gas_price: [ + {env: "GAS_PRICE"}, + {default: "12000000000"} + ], + options: { + transactionConfirmationBlocks: 1, + transactionBlockTimeout: 5 + } + }, + accounts: [ + {env: "ACCOUNT"}, + {file: "~/.ethereum/kovan"}, // Load from given file with contents as the private key (e.g. 0x...) + {unlocked: 0} + ] + }, mainnet: { providers: [ {env: "PROVIDER"},