├── .gitignore ├── LICENSE ├── README.md ├── contracts └── MyContract.sol ├── examples ├── addresses.json ├── borrow-repay-example.js ├── claim-reward-example.js ├── common.js ├── get-a-price.js ├── get-apr-example.js ├── get-asset-price-feeds.js ├── get-borrowable-amount.js ├── get-cf-examples.js ├── get-principal-example.js ├── get-supported-assets.js ├── interest-earned-example.js ├── repay-full-borrow-example.js ├── supply-withdraw-example.js └── tvl-example.js ├── hardhat.config.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | cache/ 4 | artifacts/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Compound Labs, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compound III Developer FAQ 2 | 3 | This repository contains code examples for frequent Compound III developer tasks. 4 | 5 | ## Pattern Examples for Developers 6 | 7 | See `contracts/MyContract.sol` for **Solidity examples**, and also the individual **JavaScript files** in `examples/` for the following cases: 8 | 9 | - How do I supply collateral to Compound III? ([supply-withdraw-example.js](examples/supply-withdraw-example.js)) 10 | - How do I borrow the base asset from Compound III? ([borrow-repay-example.js](examples/borrow-repay-example.js)) 11 | - How do I get an asset price from the Compound III protocol's perspective? ([get-a-price.js](examples/get-a-price.js)) 12 | - How do I get the supported asset addresses from Compound III? ([get-supported-assets.js](examples/get-supported-assets.js)) 13 | - How do I get the price feed addresses for assets supported by an instance of Compound III?([get-asset-price-feeds.js](examples/get-asset-price-feeds.js)) 14 | - How do I get the Supply or Borrow APR from the protocol? ([get-apr-example.js](examples/get-apr-example.js)) 15 | - How do I get the borrow capacity for a Compound III account? ([get-borrowable-amount.js](examples/get-borrowable-amount.js)) 16 | - How do I get the borrow and liquidate collateral factors for a Compound III asset? ([get-cf-examples.js](examples/get-cf-examples.js)) 17 | - How do I get the principal amount of asset for a Compound III account? ([get-principal-example.js](examples/get-principal-example.js)) 18 | - How do I calculate the interest earned by a Compound III account? ([interest-earned-example.js](examples/interest-earned-example.js)) 19 | - How do I repay my whole borrow precisely? ([repay-full-borrow-example.js](examples/repay-full-borrow-example.js)) 20 | - How do I calculate the APR of COMP rewards? ([get-apr-example.js](examples/get-apr-example.js)) 21 | - How do I find out the amount of COMP rewards currently accrued for my account? ([claim-reward-example.js](examples/claim-reward-example.js)) 22 | - How do I find out the TVL? ([tvl-example.js](examples/tvl-example.js)) 23 | 24 | ## Running The Examples 25 | 26 | First install all of this repository's dependencies. 27 | 28 | ``` 29 | npm install 30 | ``` 31 | 32 | Be sure to set your JSON RPC provider URL at the top of `hardhat.config.js`. Also pick the supported Comet instance to run tests against locally. 33 | 34 | ```js 35 | const providerUrl = 'https://eth-mainnet.alchemyapi.io/v2/__YOUR_API_KEY_HERE__'; 36 | const cometInstance = 'usdc-mainnet'; 37 | ``` 38 | 39 | Use the mocha descriptions to run subsets of tests. The Comet instances supported by the tests are listed in `hardhat.config.js`. 40 | 41 | - To run all tests: `npm test`. With the environment variable if not set already `MAINNET_PROVIDER_URL="__Alchemy_or_Infura_provider_URL_here__" npm test`. 42 | - To run a single file's tests: `npm test -- -g "Find an account's Compound III base asset interest earned"` 43 | - Use the description in the top (whole file) level describe block for the test file. 44 | - To run a single test: `npm test -- -g 'Finds the interest earned of base asset'` 45 | - Use the description in the test level describe block. 46 | 47 | ## How do I call Comet methods? From off chain? From Solidity? 48 | 49 | Compound III has several contract files that make up the public Comet interface. The address of the Compound III upgradable proxy contract is used to call methods in Comet.sol, CometExt.sol, and CometCore.sol. 50 | 51 | To get the ABI for Comet, run the build process in the [Compound III repository](https://github.com/compound-finance/comet). Look for the artifact of `CometInterface.sol` in the generated Hardhat artifacts folder. 52 | 53 | ```bash 54 | ## First, run the build command in the Compound III project repository 55 | git clone https://github.com/compound-finance/comet.git 56 | cd comet/ 57 | yarn run build 58 | ``` 59 | 60 | ```js 61 | // Reference the Hardhat artifact in the Compound III project build files 62 | const abi = require('./artifacts/contracts/CometInterface.sol/CometInterface.json').abi; 63 | 64 | const comet = new ethers.Contract(cometAddress, abi, provider); 65 | ``` 66 | 67 | ```solidity 68 | pragma solidity 0.8.13; 69 | 70 | import "./CometInterface.sol"; 71 | 72 | contract MyContract { //... 73 | ``` 74 | 75 | ## How do I get the latest contract addresses? 76 | 77 | Use the spider functionality in the Compound III repository. The addresses can then be found in the `deployments/` folder. 78 | 79 | ``` 80 | git clone https://github.com/compound-finance/comet.git 81 | cd comet/ 82 | yarn 83 | npx hardhat spider --deployment mainnet 84 | ``` 85 | 86 | ## How do I deploy Compound III? 87 | 88 | To deploy to a public blockchain, see the `yarn deploy` instructions in the [README file of the Comet repository](https://github.com/compound-finance/comet#multi-chain-support). Be sure to first use the `spider` command to pull in the network's existing configuration and latest contract addresses. 89 | 90 | Compound III can be deployed to EVM compatible blockchains. Here is an example for deploying to a locally run Ethereum node. 91 | 92 | ``` 93 | ## In one command line window: 94 | git clone https://github.com/compound-finance/comet.git 95 | cd comet/ 96 | yarn install 97 | 98 | ## This runs the ethereum node locally 99 | ## The development mnemonic or private keys can be configured in hardhat.config.ts 100 | npx hardhat node 101 | 102 | ## In another command line window: 103 | cd comet/ 104 | 105 | ## This deploys to the running local ethereum node 106 | ## It also writes deployment information to ./deployments/localhost/ 107 | yarn deploy --network localhost 108 | ``` 109 | -------------------------------------------------------------------------------- /contracts/MyContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.11; 4 | 5 | library CometStructs { 6 | struct AssetInfo { 7 | uint8 offset; 8 | address asset; 9 | address priceFeed; 10 | uint64 scale; 11 | uint64 borrowCollateralFactor; 12 | uint64 liquidateCollateralFactor; 13 | uint64 liquidationFactor; 14 | uint128 supplyCap; 15 | } 16 | 17 | struct UserBasic { 18 | int104 principal; 19 | uint64 baseTrackingIndex; 20 | uint64 baseTrackingAccrued; 21 | uint16 assetsIn; 22 | uint8 _reserved; 23 | } 24 | 25 | struct TotalsBasic { 26 | uint64 baseSupplyIndex; 27 | uint64 baseBorrowIndex; 28 | uint64 trackingSupplyIndex; 29 | uint64 trackingBorrowIndex; 30 | uint104 totalSupplyBase; 31 | uint104 totalBorrowBase; 32 | uint40 lastAccrualTime; 33 | uint8 pauseFlags; 34 | } 35 | 36 | struct UserCollateral { 37 | uint128 balance; 38 | uint128 _reserved; 39 | } 40 | 41 | struct RewardOwed { 42 | address token; 43 | uint owed; 44 | } 45 | 46 | struct TotalsCollateral { 47 | uint128 totalSupplyAsset; 48 | uint128 _reserved; 49 | } 50 | } 51 | 52 | interface Comet { 53 | function baseScale() external view returns (uint); 54 | function supply(address asset, uint amount) external; 55 | function withdraw(address asset, uint amount) external; 56 | 57 | function getSupplyRate(uint utilization) external view returns (uint); 58 | function getBorrowRate(uint utilization) external view returns (uint); 59 | 60 | function getAssetInfoByAddress(address asset) external view returns (CometStructs.AssetInfo memory); 61 | function getAssetInfo(uint8 i) external view returns (CometStructs.AssetInfo memory); 62 | 63 | 64 | function getPrice(address priceFeed) external view returns (uint128); 65 | 66 | function userBasic(address) external view returns (CometStructs.UserBasic memory); 67 | function totalsBasic() external view returns (CometStructs.TotalsBasic memory); 68 | function userCollateral(address, address) external view returns (CometStructs.UserCollateral memory); 69 | 70 | function baseTokenPriceFeed() external view returns (address); 71 | 72 | function numAssets() external view returns (uint8); 73 | 74 | function getUtilization() external view returns (uint); 75 | 76 | function baseTrackingSupplySpeed() external view returns (uint); 77 | function baseTrackingBorrowSpeed() external view returns (uint); 78 | 79 | function totalSupply() external view returns (uint256); 80 | function totalBorrow() external view returns (uint256); 81 | 82 | function baseIndexScale() external pure returns (uint64); 83 | 84 | function totalsCollateral(address asset) external view returns (CometStructs.TotalsCollateral memory); 85 | 86 | function baseMinForRewards() external view returns (uint256); 87 | function baseToken() external view returns (address); 88 | } 89 | 90 | interface CometRewards { 91 | function getRewardOwed(address comet, address account) external returns (CometStructs.RewardOwed memory); 92 | function claim(address comet, address src, bool shouldAccrue) external; 93 | } 94 | 95 | interface ERC20 { 96 | function approve(address spender, uint256 amount) external returns (bool); 97 | function decimals() external view returns (uint); 98 | } 99 | 100 | contract MyContract { 101 | address public cometAddress; 102 | uint constant public DAYS_PER_YEAR = 365; 103 | uint constant public SECONDS_PER_DAY = 60 * 60 * 24; 104 | uint constant public SECONDS_PER_YEAR = SECONDS_PER_DAY * DAYS_PER_YEAR; 105 | uint public BASE_MANTISSA; 106 | uint public BASE_INDEX_SCALE; 107 | uint constant public MAX_UINT = type(uint).max; 108 | 109 | event AssetInfoLog(CometStructs.AssetInfo); 110 | event LogUint(string, uint); 111 | event LogAddress(string, address); 112 | 113 | constructor(address _cometAddress) { 114 | cometAddress = _cometAddress; 115 | BASE_MANTISSA = Comet(cometAddress).baseScale(); 116 | BASE_INDEX_SCALE = Comet(cometAddress).baseIndexScale(); 117 | } 118 | 119 | /* 120 | * Supply an asset that this contract holds to Compound III 121 | */ 122 | function supply(address asset, uint amount) public { 123 | ERC20(asset).approve(cometAddress, amount); 124 | Comet(cometAddress).supply(asset, amount); 125 | } 126 | 127 | /* 128 | * Withdraws an asset from Compound III to this contract 129 | */ 130 | function withdraw(address asset, uint amount) public { 131 | Comet(cometAddress).withdraw(asset, amount); 132 | } 133 | 134 | /* 135 | * Repays an entire borrow of the base asset from Compound III 136 | */ 137 | function repayFullBorrow(address baseAsset) public { 138 | ERC20(baseAsset).approve(cometAddress, MAX_UINT); 139 | Comet(cometAddress).supply(baseAsset, MAX_UINT); 140 | } 141 | 142 | /* 143 | * Get the current supply APR in Compound III 144 | */ 145 | function getSupplyApr() public view returns (uint) { 146 | Comet comet = Comet(cometAddress); 147 | uint utilization = comet.getUtilization(); 148 | return comet.getSupplyRate(utilization) * SECONDS_PER_YEAR * 100; 149 | } 150 | 151 | /* 152 | * Get the current borrow APR in Compound III 153 | */ 154 | function getBorrowApr() public view returns (uint) { 155 | Comet comet = Comet(cometAddress); 156 | uint utilization = comet.getUtilization(); 157 | return comet.getBorrowRate(utilization) * SECONDS_PER_YEAR * 100; 158 | } 159 | 160 | /* 161 | * Get the current reward for supplying APR in Compound III 162 | * @param rewardTokenPriceFeed The address of the reward token (e.g. COMP) price feed 163 | * @return The reward APR in USD as a decimal scaled up by 1e18 164 | */ 165 | function getRewardAprForSupplyBase(address rewardTokenPriceFeed) public view returns (uint) { 166 | Comet comet = Comet(cometAddress); 167 | uint rewardTokenPriceInUsd = getCompoundPrice(rewardTokenPriceFeed); 168 | uint usdcPriceInUsd = getCompoundPrice(comet.baseTokenPriceFeed()); 169 | uint usdcTotalSupply = comet.totalSupply(); 170 | uint baseTrackingSupplySpeed = comet.baseTrackingSupplySpeed(); 171 | uint rewardToSuppliersPerDay = baseTrackingSupplySpeed * SECONDS_PER_DAY * (BASE_INDEX_SCALE / BASE_MANTISSA); 172 | uint supplyBaseRewardApr = (rewardTokenPriceInUsd * rewardToSuppliersPerDay / (usdcTotalSupply * usdcPriceInUsd)) * DAYS_PER_YEAR; 173 | return supplyBaseRewardApr; 174 | } 175 | 176 | /* 177 | * Get the current reward for borrowing APR in Compound III 178 | * @param rewardTokenPriceFeed The address of the reward token (e.g. COMP) price feed 179 | * @return The reward APR in USD as a decimal scaled up by 1e18 180 | */ 181 | function getRewardAprForBorrowBase(address rewardTokenPriceFeed) public view returns (uint) { 182 | Comet comet = Comet(cometAddress); 183 | uint rewardTokenPriceInUsd = getCompoundPrice(rewardTokenPriceFeed); 184 | uint usdcPriceInUsd = getCompoundPrice(comet.baseTokenPriceFeed()); 185 | uint usdcTotalBorrow = comet.totalBorrow(); 186 | uint baseTrackingBorrowSpeed = comet.baseTrackingBorrowSpeed(); 187 | uint rewardToSuppliersPerDay = baseTrackingBorrowSpeed * SECONDS_PER_DAY * (BASE_INDEX_SCALE / BASE_MANTISSA); 188 | uint borrowBaseRewardApr = (rewardTokenPriceInUsd * rewardToSuppliersPerDay / (usdcTotalBorrow * usdcPriceInUsd)) * DAYS_PER_YEAR; 189 | return borrowBaseRewardApr; 190 | } 191 | 192 | /* 193 | * Get the amount of base asset that can be borrowed by an account 194 | * scaled up by 10 ^ 8 195 | */ 196 | function getBorrowableAmount(address account) public view returns (int) { 197 | Comet comet = Comet(cometAddress); 198 | uint8 numAssets = comet.numAssets(); 199 | uint16 assetsIn = comet.userBasic(account).assetsIn; 200 | uint64 si = comet.totalsBasic().baseSupplyIndex; 201 | uint64 bi = comet.totalsBasic().baseBorrowIndex; 202 | address baseTokenPriceFeed = comet.baseTokenPriceFeed(); 203 | 204 | int liquidity = int( 205 | presentValue(comet.userBasic(account).principal, si, bi) * 206 | int256(getCompoundPrice(baseTokenPriceFeed)) / 207 | int256(1e8) 208 | ); 209 | 210 | for (uint8 i = 0; i < numAssets; i++) { 211 | if (isInAsset(assetsIn, i)) { 212 | CometStructs.AssetInfo memory asset = comet.getAssetInfo(i); 213 | uint newAmount = uint(comet.userCollateral(account, asset.asset).balance) * getCompoundPrice(asset.priceFeed) / 1e8; 214 | liquidity += int( 215 | newAmount * asset.borrowCollateralFactor / 1e18 216 | ); 217 | } 218 | } 219 | 220 | return liquidity; 221 | } 222 | 223 | /* 224 | * Get the borrow collateral factor for an asset 225 | */ 226 | function getBorrowCollateralFactor(address asset) public view returns (uint) { 227 | Comet comet = Comet(cometAddress); 228 | return comet.getAssetInfoByAddress(asset).borrowCollateralFactor; 229 | } 230 | 231 | /* 232 | * Get the liquidation collateral factor for an asset 233 | */ 234 | function getLiquidateCollateralFactor(address asset) public view returns (uint) { 235 | Comet comet = Comet(cometAddress); 236 | return comet.getAssetInfoByAddress(asset).liquidateCollateralFactor; 237 | } 238 | 239 | /* 240 | * Get the price feed address for an asset 241 | */ 242 | function getPriceFeedAddress(address asset) public view returns (address) { 243 | Comet comet = Comet(cometAddress); 244 | return comet.getAssetInfoByAddress(asset).priceFeed; 245 | } 246 | 247 | /* 248 | * Get the price feed address for the base token 249 | */ 250 | function getBaseTokenPriceFeed() public view returns (address) { 251 | Comet comet = Comet(cometAddress); 252 | return comet.baseTokenPriceFeed(); 253 | } 254 | 255 | /* 256 | * Get the current price of an asset from the protocol's persepctive 257 | */ 258 | function getCompoundPrice(address singleAssetPriceFeed) public view returns (uint) { 259 | Comet comet = Comet(cometAddress); 260 | return comet.getPrice(singleAssetPriceFeed); 261 | } 262 | 263 | /* 264 | * Gets the amount of reward tokens due to this contract address 265 | */ 266 | function getRewardsOwed(address rewardsContract) public returns (uint) { 267 | return CometRewards(rewardsContract).getRewardOwed(cometAddress, address(this)).owed; 268 | } 269 | 270 | /* 271 | * Claims the reward tokens due to this contract address 272 | */ 273 | function claimCometRewards(address rewardsContract) public { 274 | CometRewards(rewardsContract).claim(cometAddress, address(this), true); 275 | } 276 | 277 | /* 278 | * Gets the Compound III TVL in USD scaled up by 1e8 279 | */ 280 | function getTvl() public view returns (uint) { 281 | Comet comet = Comet(cometAddress); 282 | 283 | uint baseScale = 10 ** ERC20(cometAddress).decimals(); 284 | uint basePrice = getCompoundPrice(comet.baseTokenPriceFeed()); 285 | uint totalSupplyBase = comet.totalSupply(); 286 | 287 | uint tvlUsd = totalSupplyBase * basePrice / baseScale; 288 | 289 | uint8 numAssets = comet.numAssets(); 290 | for (uint8 i = 0; i < numAssets; i++) { 291 | CometStructs.AssetInfo memory asset = comet.getAssetInfo(i); 292 | CometStructs.TotalsCollateral memory tc = comet.totalsCollateral(asset.asset); 293 | uint price = getCompoundPrice(asset.priceFeed); 294 | uint scale = 10 ** ERC20(asset.asset).decimals(); 295 | 296 | tvlUsd += tc.totalSupplyAsset * price / scale; 297 | } 298 | 299 | return tvlUsd; 300 | } 301 | 302 | /* 303 | * Demonstrates how to get information about all assets supported 304 | */ 305 | function getAllAssetInfos() public { 306 | Comet comet = Comet(cometAddress); 307 | uint8 numAssets = comet.numAssets(); 308 | 309 | for (uint8 i = 0; i < numAssets; i++) { 310 | CometStructs.AssetInfo memory asset = comet.getAssetInfo(i); 311 | emit AssetInfoLog(asset); 312 | } 313 | 314 | emit LogUint('baseMinForRewards', comet.baseMinForRewards()); 315 | emit LogUint('baseScale', comet.baseScale()); 316 | emit LogAddress('baseToken', comet.baseToken()); 317 | emit LogAddress('baseTokenPriceFeed', comet.baseTokenPriceFeed()); 318 | emit LogUint('baseTrackingBorrowSpeed', comet.baseTrackingBorrowSpeed()); 319 | emit LogUint('baseTrackingSupplySpeed', comet.baseTrackingSupplySpeed()); 320 | } 321 | 322 | function presentValue( 323 | int104 principalValue_, 324 | uint64 baseSupplyIndex_, 325 | uint64 baseBorrowIndex_ 326 | ) internal view returns (int104) { 327 | if (principalValue_ >= 0) { 328 | return int104(uint104(principalValue_) * baseSupplyIndex_ / uint64(BASE_INDEX_SCALE)); 329 | } else { 330 | return -int104(uint104(principalValue_) * baseBorrowIndex_ / uint64(BASE_INDEX_SCALE)); 331 | } 332 | } 333 | 334 | function isInAsset(uint16 assetsIn, uint8 assetOffset) internal pure returns (bool) { 335 | return (assetsIn & (uint16(1) << assetOffset) != 0); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /examples/addresses.json: -------------------------------------------------------------------------------- 1 | { 2 | "usdc-mainnet": { 3 | "comet": "0xc3d688B66703497DAA19211EEdff47f25384cdc3", 4 | "bulker": "0x74a81F84268744a40FEBc48f8b812a1f188D80C3", 5 | "configurator": "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3", 6 | "rewards": "0x1B0e765F6224C21223AeA2af16c1C46E38885a40", 7 | "WBTC": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", 8 | "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 9 | "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 10 | "COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888", 11 | "UNI": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", 12 | "LINK": "0x514910771AF9Ca656af840dff83E8264EcF986CA", 13 | "WBTC_Price_Feed": "0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c", 14 | "USDC_Price_Feed": "0x8fffffd4afb6115b954bd326cbe7b4ba576818f6", 15 | "WETH_Price_Feed": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", 16 | "COMP_Price_Feed": "0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5", 17 | "UNI_Price_Feed": "0x553303d460EE0afB37EdFf9bE42922D8FF63220e", 18 | "LINK_Price_Feed": "0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c" 19 | }, 20 | "usdc-goerli": { 21 | "comet": "0x3EE77595A8459e93C2888b13aDB354017B198188", 22 | "bulker": "0xf82AAB8ae0E7F6a2ecBfe2375841d83AeA4cb9cE", 23 | "configurator": "0xB28495db3eC65A0e3558F040BC4f98A0d588Ae60", 24 | "rewards": "0xef9e070044d62C38D2e316146dDe92AD02CF2c2c", 25 | "fauceteer": "0x75442Ac771a7243433e033F3F8EaB2631e22938f", 26 | "WBTC": "0xAAD4992D949f9214458594dF92B44165Fb84dC19", 27 | "USDC": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F", 28 | "WETH": "0x42a71137C09AE83D8d05974960fd607d40033499", 29 | "COMP": "0x3587b2F7E0E2D6166d6C14230e7Fe160252B0ba4", 30 | "WBTC_Price_Feed": "0xA39434A63A52E749F02807ae27335515BA4b07F7", 31 | "USDC_Price_Feed": "0xAb5c49580294Aff77670F839ea425f5b78ab3Ae7", 32 | "WETH_Price_Feed": "0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e", 33 | "COMP_Price_Feed": "0x54a06047087927D9B0fb21c1cf0ebd792764dDB8" 34 | }, 35 | "usdc-polygon": { 36 | "comet": "0xF25212E676D1F7F89Cd72fFEe66158f541246445", 37 | "bulker": "0x59e242D352ae13166B4987aE5c990C232f7f7CD6", 38 | "configurator": "0x83E0F742cAcBE66349E3701B171eE2487a26e738", 39 | "rewards": "0x45939657d1CA34A8FA39A924B71D28Fe8431e581", 40 | "COMP": "0x8505b9d2254A7Ae468c0E9dd10Ccea3A837aef5c", 41 | "MaticX": "0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6", 42 | "stMATIC": "0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4", 43 | "USDC": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", 44 | "WBTC": "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", 45 | "WETH": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", 46 | "WMATIC": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", 47 | "COMP_Price_Feed": "0x2A8758b7257102461BC958279054e372C2b1bDE6", 48 | "MaticX_Price_Feed": "0x5d37E4b374E6907de8Fc7fb33EE3b0af403C7403", 49 | "stMATIC_Price_Feed": "0x97371dF4492605486e23Da797fA68e55Fc38a13f", 50 | "USDC_Price_Feed": "0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7", 51 | "WBTC_Price_Feed": "0xDE31F8bFBD8c84b5360CFACCa3539B938dd78ae6", 52 | "WETH_Price_Feed": "0xF9680D99D6C9589e2a93a78A04A279e509205945", 53 | "WMATIC_Price_Feed": "0xAB594600376Ec9fD91F8e885dADF0CE036862dE0" 54 | } 55 | } -------------------------------------------------------------------------------- /examples/borrow-repay-example.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const myContractAbi = [ 14 | 'function supply(address asset, uint amount) public', 15 | 'function withdraw(address asset, uint amount) public', 16 | ]; 17 | 18 | const cometAbi = [ 19 | 'event Supply(address indexed from, address indexed dst, uint256 amount)', 20 | 'function supply(address asset, uint amount)', 21 | 'function withdraw(address asset, uint amount)', 22 | 'function balanceOf(address account) returns (uint256)', 23 | 'function borrowBalanceOf(address account) returns (uint256)', 24 | 'function collateralBalanceOf(address account, address asset) external view returns (uint128)', 25 | ]; 26 | 27 | const wethAbi = [ 28 | 'function deposit() payable', 29 | 'function balanceOf(address) returns (uint)', 30 | 'function approve(address, uint) returns (bool)', 31 | 'function transfer(address, uint)', 32 | ]; 33 | 34 | const stdErc20Abi = [ 35 | 'function approve(address, uint) returns (bool)', 36 | 'function transfer(address, uint)', 37 | 'function balanceOf(address) returns (uint)', 38 | ]; 39 | 40 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress, wethAddress; 41 | 42 | const mnemonic = hre.network.config.accounts.mnemonic; 43 | const addresses = []; 44 | const privateKeys = []; 45 | for (let i = 0; i < 20; i++) { 46 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 47 | addresses.push(wallet.address); 48 | privateKeys.push(wallet._signingKey().privateKey); 49 | } 50 | 51 | describe("Compound III Borrow Examples", function () { 52 | before(async () => { 53 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 54 | 55 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 56 | hostname: '127.0.0.1', 57 | port: 8545, 58 | provider: hre.network.provider 59 | }); 60 | 61 | await jsonRpcServer.listen(); 62 | 63 | baseAssetAddress = networks[net].USDC; 64 | usdcAddress = baseAssetAddress; 65 | cometAddress = networks[net].comet; 66 | wethAddress = networks[net].WETH; 67 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 68 | }); 69 | 70 | beforeEach(async () => { 71 | await resetForkedChain(hre, providerUrl, blockNumber); 72 | deployment = await myContractFactory.deploy(cometAddress); 73 | }); 74 | 75 | after(async () => { 76 | await jsonRpcServer.close(); 77 | }); 78 | 79 | it('Borrows the base asset from Compound using JS', async () => { 80 | const me = addresses[0]; 81 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 82 | const signer = provider.getSigner(me); 83 | const comet = new ethers.Contract(cometAddress, cometAbi, signer); 84 | const weth = new ethers.Contract(wethAddress, wethAbi, signer); 85 | const wethMantissa = 1e18; // WETH and ETH have 18 decimal places 86 | const usdc = new ethers.Contract(baseAssetAddress, stdErc20Abi, signer); 87 | const baseAssetMantissa = 1e6; // USDC has 6 decimal places 88 | 89 | let tx = await weth.deposit({ value: ethers.utils.parseEther('10') }); 90 | await tx.wait(1); 91 | 92 | console.log('\tApproving Comet to move WETH collateral...'); 93 | tx = await weth.approve(cometAddress, ethers.constants.MaxUint256); 94 | await tx.wait(1); 95 | 96 | console.log('\tSending initial WETH supply of collateral to Compound...'); 97 | tx = await comet.supply(wethAddress, ethers.utils.parseEther('10')); 98 | await tx.wait(1); 99 | 100 | // Accounts cannot hold a borrow smaller than baseBorrowMin (100 USDC). 101 | const borrowSize = 1000; 102 | console.log('\tExecuting initial borrow of the base asset from Compound...'); 103 | console.log('\tBorrow size:', borrowSize); 104 | 105 | tx = await comet.withdraw(usdcAddress, (borrowSize * baseAssetMantissa).toString()); 106 | await tx.wait(1); 107 | 108 | let bal = await usdc.callStatic.balanceOf(me); 109 | console.log('\tMy current base asset balance:', +bal.toString() / baseAssetMantissa); 110 | 111 | // Repay some of the open borrow 112 | const repayAmount = 250; // USDC 113 | 114 | console.log('\tApproving to repay the borrow...', repayAmount); 115 | tx = await usdc.approve(cometAddress, (repayAmount * baseAssetMantissa).toString()); 116 | await tx.wait(1); 117 | 118 | // We don't have enough of the base asset to repay the borrow principal plus accrued interest 119 | console.log('\tRepaying some of the open borrow...', repayAmount); 120 | tx = await comet.supply(usdcAddress, repayAmount * baseAssetMantissa); 121 | await tx.wait(1); 122 | 123 | bal = await usdc.callStatic.balanceOf(me); 124 | console.log('\tMy current base asset balance:', +bal.toString() / baseAssetMantissa); 125 | }); 126 | 127 | it('Borrows the base asset from Compound using solidity', async () => { 128 | const me = addresses[0]; 129 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 130 | const signer = provider.getSigner(me); 131 | const comet = new ethers.Contract(cometAddress, cometAbi, signer); 132 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, signer); 133 | const weth = new ethers.Contract(wethAddress, wethAbi, signer); 134 | const wethMantissa = 1e18; // WETH and ETH have 18 decimal places 135 | const usdc = new ethers.Contract(baseAssetAddress, stdErc20Abi, signer); 136 | const baseAssetMantissa = 1e6; // USDC has 6 decimal places 137 | 138 | let tx = await weth.deposit({ value: ethers.utils.parseEther('10') }); 139 | await tx.wait(1); 140 | 141 | console.log('\tTransferring WETH to MyContract to use as collateral...'); 142 | tx = await weth.transfer(MyContract.address, ethers.utils.parseEther('10')); 143 | await tx.wait(1); 144 | 145 | console.log('\tSending initial supply to Compound...'); 146 | tx = await MyContract.supply(wethAddress, ethers.utils.parseEther('10')); 147 | await tx.wait(1); 148 | 149 | // Accounts cannot hold a borrow smaller than baseBorrowMin (100 USDC). 150 | const borrowSize = 1000; 151 | console.log('\tExecuting initial borrow of the base asset from Compound...'); 152 | console.log('\tBorrow size:', borrowSize); 153 | 154 | tx = await MyContract.withdraw(usdcAddress, (borrowSize * baseAssetMantissa).toString()); 155 | await tx.wait(1); 156 | 157 | let bal = await usdc.callStatic.balanceOf(MyContract.address); 158 | console.log("\tMyContract's current base asset balance:", +bal.toString() / baseAssetMantissa); 159 | 160 | // Repay some of the open borrow 161 | const repayAmount = 250; // USDC 162 | 163 | // We don't have enough of the base asset to repay the borrow principal plus accrued interest 164 | console.log('\tRepaying some of the open borrow...', repayAmount); 165 | tx = await MyContract.supply(usdcAddress, repayAmount * baseAssetMantissa); 166 | await tx.wait(1); 167 | 168 | bal = await usdc.callStatic.balanceOf(MyContract.address); 169 | console.log("\tMyContract's current base asset balance:", +bal.toString() / baseAssetMantissa); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /examples/claim-reward-example.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const myContractAbi = [ 14 | 'function getRewardsOwed(address rewardsContract) public view returns (uint)', 15 | 'function claimCometRewards(address rewardsContract) public', 16 | ]; 17 | 18 | const rewardAbi = [ 19 | 'function getRewardOwed(address comet, address account) external returns (address token, uint owed)', 20 | 'function claim(address comet, address src, bool shouldAccrue) external', 21 | ]; 22 | 23 | let jsonRpcServer, deployment, cometAddress, rewardsAddress, compAddress, myContractFactory; 24 | 25 | const mnemonic = hre.network.config.accounts.mnemonic; 26 | const addresses = []; 27 | const privateKeys = []; 28 | for (let i = 0; i < 20; i++) { 29 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 30 | addresses.push(wallet.address); 31 | privateKeys.push(wallet._signingKey().privateKey); 32 | } 33 | 34 | describe("Reward token operations for Compound III", function () { 35 | before(async () => { 36 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 37 | 38 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 39 | hostname: '127.0.0.1', 40 | port: 8545, 41 | provider: hre.network.provider 42 | }); 43 | 44 | await jsonRpcServer.listen(); 45 | 46 | cometAddress = networks[net].comet; 47 | rewardsAddress = networks[net].rewards; 48 | compAddress = networks[net].COMP; 49 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 50 | }); 51 | 52 | beforeEach(async () => { 53 | await resetForkedChain(hre, providerUrl, blockNumber); 54 | deployment = await myContractFactory.deploy(cometAddress); 55 | }); 56 | 57 | after(async () => { 58 | await jsonRpcServer.close(); 59 | }); 60 | 61 | it('Gets the current amount of reward token due to an account using JS', async () => { 62 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 63 | const rewards = new ethers.Contract(rewardsAddress, rewardAbi, provider); 64 | 65 | const [ tokenAddress, amount ] = await rewards.callStatic.getRewardOwed(cometAddress, addresses[0]); 66 | const compTokenMantissa = 1e18; 67 | 68 | console.log('\tReward token is COMP', tokenAddress === compAddress); 69 | 70 | console.log('\tJS - COMP token rewards due to the account', +amount.toString() / compTokenMantissa); 71 | }); 72 | 73 | it('Claims and transfers the current amount of reward token due to an account using JS', async () => { 74 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 75 | const signer = provider.getSigner(addresses[0]); 76 | const rewards = new ethers.Contract(rewardsAddress, rewardAbi, signer); 77 | 78 | let tx = await rewards.claim(cometAddress, addresses[0], true); 79 | await tx.wait(1); 80 | 81 | // If due rewards, check to see if the account's COMP balance increased 82 | 83 | console.log('\tJS - COMP tokens claimed and transferred to the account'); 84 | }); 85 | 86 | it('Runs rewards examples using Solidity', async () => { 87 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 88 | const signer = provider.getSigner(addresses[0]); 89 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, signer); 90 | 91 | const amount = await MyContract.callStatic.getRewardsOwed(rewardsAddress); 92 | const compTokenMantissa = 1e18; 93 | console.log('\tSolidity - COMP token rewards due to the account', +amount.toString() / compTokenMantissa); 94 | 95 | let tx = await MyContract.claimCometRewards(rewardsAddress); 96 | await tx.wait(1); 97 | console.log('\tSolidity - Reward tokens successfully claimed for contract.'); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /examples/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resetForkedChain: async function (hre, providerUrl, blockNumber) { 3 | // Parent directory's hardhat.config.js needs these to be set 4 | await hre.network.provider.request({ 5 | method: 'hardhat_reset', 6 | params: [{ 7 | forking: { 8 | jsonRpcUrl: providerUrl, 9 | blockNumber, 10 | }, 11 | gasPrice: 0, 12 | initialBaseFeePerGas: 0, 13 | }] 14 | }); 15 | } 16 | }; -------------------------------------------------------------------------------- /examples/get-a-price.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const networks = require('./addresses.json'); 6 | const net = hre.config.cometInstance; 7 | 8 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 9 | const providerUrl = hre.config.networks.hardhat.forking.url; 10 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 11 | 12 | const myContractAbi = [ 13 | 'function getCompoundPrice(address priceFeed) public view returns (uint)', 14 | ]; 15 | 16 | const cometAbi = [ 17 | 'function getPrice(address priceFeed) public view returns (uint)', 18 | ]; 19 | 20 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress; 21 | 22 | const mnemonic = hre.network.config.accounts.mnemonic; 23 | const addresses = []; 24 | const privateKeys = []; 25 | for (let i = 0; i < 20; i++) { 26 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 27 | addresses.push(wallet.address); 28 | privateKeys.push(wallet._signingKey().privateKey); 29 | } 30 | 31 | describe("Find out an asset's present price according to Compound III", function () { 32 | before(async () => { 33 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 34 | 35 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 36 | hostname: '127.0.0.1', 37 | port: 8545, 38 | provider: hre.network.provider 39 | }); 40 | 41 | await jsonRpcServer.listen(); 42 | 43 | cometAddress = networks[net].comet; 44 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 45 | }); 46 | 47 | beforeEach(async () => { 48 | deployment = await myContractFactory.deploy(cometAddress); 49 | }); 50 | 51 | after(async () => { 52 | await jsonRpcServer.close(); 53 | }); 54 | 55 | it('Finds the price of WBTC and USDC using JS', async () => { 56 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 57 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 58 | const wbtc = networks[net].WBTC; 59 | const wbtcPriceFeed = networks[net].WBTC_Price_Feed; 60 | const usdc = networks[net].USDC; 61 | const usdcPriceFeed = networks[net].USDC_Price_Feed; 62 | 63 | let wbtcPrice = await comet.callStatic.getPrice(wbtcPriceFeed); 64 | wbtcPrice /= Math.pow(10, 8); 65 | 66 | let usdcPrice = await comet.callStatic.getPrice(usdcPriceFeed); 67 | usdcPrice /= Math.pow(10, 8); 68 | 69 | console.log('\tWBTC Price', wbtcPrice); 70 | console.log('\tUSDC Price', usdcPrice); 71 | }); 72 | 73 | it('Finds the price of WBTC and USDC using Solidity', async () => { 74 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 75 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, provider); 76 | 77 | const wbtcPriceFeed = networks[net].WBTC_Price_Feed; 78 | const usdcPriceFeed = networks[net].USDC_Price_Feed; 79 | 80 | let wbtcPrice = await MyContract.callStatic.getCompoundPrice(wbtcPriceFeed); 81 | wbtcPrice /= Math.pow(10, 8); 82 | 83 | let usdcPrice = await MyContract.callStatic.getCompoundPrice(usdcPriceFeed); 84 | usdcPrice /= Math.pow(10, 8); 85 | 86 | console.log('\tSolidity - WBTC Price', wbtcPrice); 87 | console.log('\tSolidity - USDC Price', usdcPrice); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /examples/get-apr-example.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const myContractAbi = [ 14 | 'function getSupplyApr() public view returns (uint)', 15 | 'function getBorrowApr() public view returns (uint)', 16 | 'function getRewardAprForSupplyBase(address rewardTokenPriceFeed) public view returns (uint)', 17 | 'function getRewardAprForBorrowBase(address rewardTokenPriceFeed) public view returns (uint)', 18 | ]; 19 | 20 | const cometAbi = [ 21 | 'function getSupplyRate(uint) public view returns (uint)', 22 | 'function getBorrowRate(uint) public view returns (uint)', 23 | 'function getUtilization() public view returns (uint)', 24 | 'function baseTokenPriceFeed() public view returns (address)', 25 | 'function getPrice(address) public view returns (uint128)', 26 | 'function totalSupply() external view returns (uint256)', 27 | 'function totalBorrow() external view returns (uint256)', 28 | 'function baseIndexScale() external pure returns (uint64)', 29 | 'function baseTrackingSupplySpeed() external view returns (uint)', 30 | 'function baseTrackingBorrowSpeed() external view returns (uint)', 31 | ]; 32 | 33 | let jsonRpcServer, deployment, cometAddress, usdcAddress, myContractFactory, compPriceFeedAddress; 34 | 35 | describe('Calculating the Compound III APRs', function () { 36 | before(async () => { 37 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 38 | 39 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 40 | hostname: '127.0.0.1', 41 | port: 8545, 42 | provider: hre.network.provider 43 | }); 44 | 45 | await jsonRpcServer.listen(); 46 | 47 | compPriceFeedAddress = networks[net].COMP_Price_Feed; 48 | usdcAddress = networks[net].USDC; 49 | cometAddress = networks[net].comet; 50 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 51 | }); 52 | 53 | beforeEach(async () => { 54 | await resetForkedChain(hre, providerUrl, blockNumber); 55 | deployment = await myContractFactory.deploy(cometAddress); 56 | }); 57 | 58 | after(async () => { 59 | await jsonRpcServer.close(); 60 | }); 61 | 62 | it('Calculates the Supply APR using JS', async () => { 63 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 64 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 65 | 66 | const secondsPerYear = 60 * 60 * 24 * 365; 67 | const utilization = await comet.callStatic.getUtilization(); 68 | const supplyRate = await comet.callStatic.getSupplyRate(utilization); 69 | const supplyApr = +(supplyRate).toString() / 1e18 * secondsPerYear * 100; 70 | console.log('\tJS - Supply APR', supplyApr, '%'); 71 | }); 72 | 73 | it('Calculates the Borrow APR using JS', async () => { 74 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 75 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 76 | 77 | const secondsPerYear = 60 * 60 * 24 * 365; 78 | const utilization = await comet.callStatic.getUtilization(); 79 | const borrowRate = await comet.callStatic.getBorrowRate(utilization); 80 | const borrowApr = +(borrowRate).toString() / 1e18 * secondsPerYear * 100; 81 | console.log('\tJS - Borrow APR', borrowApr, '%'); 82 | }); 83 | 84 | it('Calculates the COMP Reward APRs using JS', async () => { 85 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 86 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 87 | 88 | const priceFeedMantissa = 1e8; 89 | const usdcMantissa = 1e6; 90 | const secondsPerDay = 60 * 60 * 24; 91 | const daysInYear = 365; 92 | const baseIndexScale = +(await comet.callStatic.baseIndexScale()); 93 | const totalSupply = await comet.callStatic.totalSupply(); 94 | const totalBorrow = await comet.callStatic.totalBorrow(); 95 | const baseTokenPriceFeed = await comet.callStatic.baseTokenPriceFeed(); 96 | 97 | const compPriceInUsd = +(await comet.callStatic.getPrice(compPriceFeedAddress)).toString() / priceFeedMantissa; 98 | const usdcPriceInUsd = +(await comet.callStatic.getPrice(baseTokenPriceFeed)).toString() / priceFeedMantissa; 99 | 100 | const usdcTotalSupply = +totalSupply.toString() / usdcMantissa; 101 | const usdcTotalBorrow = +totalBorrow.toString() / usdcMantissa; 102 | const baseTrackingSupplySpeed = +(await comet.callStatic.baseTrackingSupplySpeed()).toString(); 103 | const baseTrackingBorrowSpeed = +(await comet.callStatic.baseTrackingBorrowSpeed()).toString(); 104 | 105 | const compToSuppliersPerDay = baseTrackingSupplySpeed / baseIndexScale * secondsPerDay; 106 | const compToBorrowersPerDay = baseTrackingBorrowSpeed / baseIndexScale * secondsPerDay; 107 | 108 | const supplyCompRewardApr = (compPriceInUsd * compToSuppliersPerDay / (usdcTotalSupply * usdcPriceInUsd)) * daysInYear * 100; 109 | const borrowCompRewardApr = (compPriceInUsd * compToBorrowersPerDay / (usdcTotalBorrow * usdcPriceInUsd)) * daysInYear * 100; 110 | 111 | console.log('\tJS - Supply Base Asset COMP Reward APR', supplyCompRewardApr, '%'); 112 | console.log('\tJS - Borrow Base Asset COMP Reward APR', borrowCompRewardApr, '%'); 113 | }); 114 | 115 | it('Calculates the COMP Reward APYs using JS', async () => { 116 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 117 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 118 | 119 | const priceFeedMantissa = 1e8; 120 | const usdcMantissa = 1e6; 121 | const secondsPerDay = 60 * 60 * 24; 122 | const daysInYear = 365; 123 | const baseIndexScale = +(await comet.callStatic.baseIndexScale()); 124 | const totalSupply = await comet.callStatic.totalSupply(); 125 | const totalBorrow = await comet.callStatic.totalBorrow(); 126 | const baseTokenPriceFeed = await comet.callStatic.baseTokenPriceFeed(); 127 | 128 | const compPriceInUsd = +(await comet.callStatic.getPrice(compPriceFeedAddress)).toString() / priceFeedMantissa; 129 | const usdcPriceInUsd = +(await comet.callStatic.getPrice(baseTokenPriceFeed)).toString() / priceFeedMantissa; 130 | 131 | const usdcTotalSupply = +totalSupply.toString() / usdcMantissa; 132 | const usdcTotalBorrow = +totalBorrow.toString() / usdcMantissa; 133 | const baseTrackingSupplySpeed = +(await comet.callStatic.baseTrackingSupplySpeed()).toString(); 134 | const baseTrackingBorrowSpeed = +(await comet.callStatic.baseTrackingBorrowSpeed()).toString(); 135 | 136 | const compToSuppliersPerDay = baseTrackingSupplySpeed / baseIndexScale * secondsPerDay; 137 | const compToBorrowersPerDay = baseTrackingBorrowSpeed / baseIndexScale * secondsPerDay; 138 | 139 | const supplyCompApy = (Math.pow((1 + (compPriceInUsd * compToSuppliersPerDay / (usdcTotalSupply * usdcPriceInUsd))), daysInYear) - 1) * 100; 140 | const borrowCompApy = (Math.pow((1 + (compPriceInUsd * compToBorrowersPerDay / (usdcTotalBorrow * usdcPriceInUsd))), daysInYear) - 1) * 100; 141 | 142 | console.log('\tJS - Supply Base Asset COMP Reward APY', supplyCompApy, '%'); 143 | console.log('\tJS - Borrow Base Asset COMP Reward APY', borrowCompApy, '%'); 144 | }); 145 | 146 | it('Runs COMP Reward APR examples using Solidity', async () => { 147 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 148 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, provider); 149 | 150 | const supplyApr = await MyContract.callStatic.getSupplyApr(); 151 | const borrowApr = await MyContract.callStatic.getBorrowApr(); 152 | const supplyCompRewardApr = await MyContract.callStatic.getRewardAprForSupplyBase(compPriceFeedAddress); 153 | const borrowCompRewardApr = await MyContract.callStatic.getRewardAprForBorrowBase(compPriceFeedAddress); 154 | 155 | console.log('\tSolidity - Supply Base Asset APR:', +(supplyApr).toString() / 1e18, '%'); 156 | console.log('\tSolidity - Borrow Base Asset APR:', +(borrowApr).toString() / 1e18, '%'); 157 | console.log('\tSolidity - Supply Base Asset COMP Reward APR:', +supplyCompRewardApr.toString() / 1e18 * 100, '%'); 158 | console.log('\tSolidity - Borrow Base Asset COMP Reward APR:', +borrowCompRewardApr.toString() / 1e18 * 100, '%'); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /examples/get-asset-price-feeds.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const networks = require('./addresses.json'); 6 | const net = hre.config.cometInstance; 7 | 8 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 9 | const providerUrl = hre.config.networks.hardhat.forking.url; 10 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 11 | 12 | function forEachWithCallback(callback, after) { 13 | const arrayCopy = JSON.parse(JSON.stringify(this)); 14 | let index = 0; 15 | const next = () => { 16 | index++; 17 | if (arrayCopy.length > 0) { 18 | callback(arrayCopy.shift(), index, next); 19 | } else { 20 | if (after) after(); 21 | } 22 | } 23 | next(); 24 | } 25 | 26 | Array.prototype.forEachWithCallback = forEachWithCallback; 27 | 28 | const myContractAbi = [ 29 | 'function getAllAssetInfos() public', 30 | `event AssetInfoLog(tuple( 31 | uint8 offset, 32 | address asset, 33 | address priceFeed, 34 | uint64 scale, 35 | uint64 borrowCollateralFactor, 36 | uint64 liquidateCollateralFactor, 37 | uint64 liquidationFactor, 38 | uint128 supplyCap 39 | ))`, 40 | 'event LogUint(string, uint)', 41 | 'event LogAddress(string, address)', 42 | 43 | 'function getPriceFeedAddress(address asset) public view returns (address)', 44 | 'function getBaseTokenPriceFeed() public view returns (address)' 45 | ]; 46 | 47 | const cometAbi = [ 48 | `function getAssetInfo(uint8 i) public view returns ( 49 | tuple( 50 | uint8 offset, 51 | address asset, 52 | address priceFeed, 53 | uint64 scale, 54 | uint64 borrowCollateralFactor, 55 | uint64 liquidateCollateralFactor, 56 | uint64 liquidationFactor, 57 | uint128 supplyCap 58 | ) memory 59 | )`, 60 | 61 | `function getAssetInfoByAddress(address) public view returns ( 62 | tuple( 63 | uint8 offset, 64 | address asset, 65 | address priceFeed, 66 | uint64 scale, 67 | uint64 borrowCollateralFactor, 68 | uint64 liquidateCollateralFactor, 69 | uint64 liquidationFactor, 70 | uint128 supplyCap 71 | ) memory 72 | )`, 73 | 74 | 'function baseBorrowMin() returns (uint)', 75 | 'function baseMinForRewards() returns (uint)', 76 | 'function baseScale() returns (uint)', 77 | 'function baseToken() returns (address)', 78 | 'function baseTokenPriceFeed() returns (address)', 79 | 'function baseTrackingBorrowSpeed() returns (uint)', 80 | 'function baseTrackingSupplySpeed() returns (uint)', 81 | 82 | 'function numAssets() returns (uint)', 83 | ]; 84 | 85 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress; 86 | 87 | const mnemonic = hre.network.config.accounts.mnemonic; 88 | const addresses = []; 89 | const privateKeys = []; 90 | for (let i = 0; i < 20; i++) { 91 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 92 | addresses.push(wallet.address); 93 | privateKeys.push(wallet._signingKey().privateKey); 94 | } 95 | 96 | describe("Finds asset price feeds for an instance of Compound III", function () { 97 | before(async () => { 98 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 99 | 100 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 101 | hostname: '127.0.0.1', 102 | port: 8545, 103 | provider: hre.network.provider 104 | }); 105 | 106 | await jsonRpcServer.listen(); 107 | 108 | cometAddress = networks[net].comet; 109 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 110 | }); 111 | 112 | beforeEach(async () => { 113 | deployment = await myContractFactory.deploy(cometAddress); 114 | }); 115 | 116 | after(async () => { 117 | await jsonRpcServer.close(); 118 | }); 119 | 120 | it('Finds asset price feeds supported by a Comet instance using JS', async () => { 121 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 122 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 123 | 124 | wbtcAddress = networks[net].WBTC; 125 | 126 | // If you don't already have the asset address, use `getAssetInfo` 127 | // see `get-supported-assets.js` example file 128 | 129 | const priceFeed = (await comet.callStatic.getAssetInfoByAddress(wbtcAddress)).priceFeed; 130 | console.log('\tJS - WBTC Price Feed', priceFeed); 131 | console.log('\tJS - baseTokenPriceFeed', (await comet.callStatic.baseTokenPriceFeed()).toString()); 132 | }); 133 | 134 | it('Finds asset price feeds supported by a Comet instance using Solidity', async () => { 135 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 136 | const signer = provider.getSigner(addresses[0]); 137 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, signer); 138 | 139 | wbtcAddress = networks[net].WBTC; 140 | 141 | // If you don't already have the asset address, use `getAssetInfo` 142 | // see `get-supported-assets.js` example file 143 | 144 | const priceFeedAddress = await MyContract.callStatic.getPriceFeedAddress(wbtcAddress); 145 | const baseTokenPriceFeed = await MyContract.callStatic.getBaseTokenPriceFeed(); 146 | 147 | console.log('\tSolidity - WBTC Price Feed', priceFeedAddress); 148 | console.log('\tSolidity - baseTokenPriceFeed', baseTokenPriceFeed); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /examples/get-borrowable-amount.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const myContractAbi = [ 14 | 'function getBorrowableAmount(address account) external view returns (int)', 15 | ]; 16 | 17 | const cometAbi = [ 18 | 'function numAssets() returns (uint8)', 19 | 'function getAssetInfo(uint8 i) public view returns (tuple(uint8 offset, address asset, address priceFeed, uint64 scale, uint64 borrowCollateralFactor, uint64 liquidateCollateralFactor, uint64 liquidationFactor, uint128 supplyCap) memory)', 20 | 'function collateralBalanceOf(address account, address asset) external view returns (uint128)', 21 | 'function getPrice(address priceFeed) public view returns (uint128)', 22 | 'function baseTokenPriceFeed() public view returns (address)', 23 | 'function borrowBalanceOf(address account) external view returns (uint256)', 24 | 'function decimals() external view returns (uint)', 25 | 'function userBasic(address) public returns (tuple(int104 principal, uint64 baseTrackingIndex, uint64 baseTrackingAccrued, uint16 assetsIn, uint8 _reserved) memory)', 26 | ]; 27 | 28 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress, baseAssetPriceFeed; 29 | 30 | const mnemonic = hre.network.config.accounts.mnemonic; 31 | const addresses = []; 32 | const privateKeys = []; 33 | for (let i = 0; i < 20; i++) { 34 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 35 | addresses.push(wallet.address); 36 | privateKeys.push(wallet._signingKey().privateKey); 37 | } 38 | 39 | describe("Find an account's present limits on borrowing from Compound III", function () { 40 | before(async () => { 41 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 42 | 43 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 44 | hostname: '127.0.0.1', 45 | port: 8545, 46 | provider: hre.network.provider 47 | }); 48 | 49 | await jsonRpcServer.listen(); 50 | 51 | cometAddress = networks[net].comet; 52 | baseAssetPriceFeed = networks[net].USDC_Price_Feed; 53 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 54 | }); 55 | 56 | beforeEach(async () => { 57 | await resetForkedChain(hre, providerUrl, blockNumber); 58 | deployment = await myContractFactory.deploy(cometAddress); 59 | }); 60 | 61 | after(async () => { 62 | await jsonRpcServer.close(); 63 | }); 64 | 65 | it('Calculates the borrow capacity of an account that has supplied collateral using JS', async () => { 66 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 67 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 68 | 69 | const myAddress = addresses[0]; 70 | 71 | const numAssets = await comet.callStatic.numAssets(); 72 | 73 | const promisesAssets = []; 74 | for (let i = 0; i < numAssets; i++) { 75 | promisesAssets.push(comet.callStatic.getAssetInfo(i)); 76 | } 77 | 78 | const infos = await Promise.all(promisesAssets); 79 | 80 | const promisesCollaterals = []; 81 | const promisesDecimals = []; 82 | const promisesPrices = []; 83 | for (let i = 0; i < numAssets; i++) { 84 | const { asset, priceFeed } = infos[i]; 85 | promisesCollaterals.push(comet.callStatic.collateralBalanceOf(myAddress, asset)); 86 | promisesPrices.push(comet.callStatic.getPrice(priceFeed)); 87 | } 88 | 89 | const collateralBalances = await Promise.all(promisesCollaterals); 90 | const collateralPrices = await Promise.all(promisesPrices); 91 | 92 | const baseTokenPriceFeed = await comet.callStatic.baseTokenPriceFeed(); 93 | const basePrice = +(await comet.callStatic.getPrice(baseTokenPriceFeed)).toString() / 1e8; 94 | const baseDecimals = +(await comet.callStatic.decimals()).toString(); 95 | 96 | let collateralValueUsd = 0; 97 | let totalBorrowCapacityUsd = 0; 98 | for (let i = 0; i < numAssets; i++) { 99 | const balance = +(collateralBalances[i].toString()) / +(infos[i].scale).toString(); 100 | const price = +collateralPrices[i].toString() / 1e8; 101 | collateralValueUsd += balance * price; 102 | totalBorrowCapacityUsd += balance * price * (+infos[i].borrowCollateralFactor.toString() / 1e18); 103 | } 104 | 105 | const borrowBalance = +(await comet.callStatic.borrowBalanceOf(myAddress)).toString(); 106 | const borrowedInUsd = borrowBalance / Math.pow(10, baseDecimals) * basePrice; 107 | 108 | const borrowCapacityUsd = totalBorrowCapacityUsd - borrowedInUsd; 109 | 110 | console.log('\tMaximum borrowable amount (USD)', borrowCapacityUsd); 111 | console.log('\tAlready Borrowed amount (USD)', borrowedInUsd); 112 | 113 | const borrowCapacityBase = borrowCapacityUsd / basePrice; 114 | const borrowedInBase = borrowedInUsd / basePrice 115 | console.log('\tMaximum borrowable amount (base)', borrowCapacityBase); 116 | console.log('\tAlready Borrowed amount (base)', borrowedInBase); 117 | }); 118 | 119 | it('Calculates the borrow capacity of an account that has supplied collateral using Solidity', async () => { 120 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 121 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, provider); 122 | 123 | const myAddress = addresses[0]; 124 | let borrowable = await MyContract.callStatic.getBorrowableAmount(myAddress); 125 | borrowable /= Math.pow(10, 8); 126 | 127 | console.log('\tSolidity - Base asset borrowable amount', borrowable); 128 | }); 129 | 130 | }); 131 | -------------------------------------------------------------------------------- /examples/get-cf-examples.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const cometAbi = [ 14 | 'event Supply(address indexed from, address indexed dst, uint256 amount)', 15 | 'function balanceOf(address account) returns (uint256)', 16 | { "inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetInfoByAddress","outputs":[{"components":[{"internalType":"uint8","name":"offset","type":"uint8"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"priceFeed","type":"address"},{"internalType":"uint64","name":"scale","type":"uint64"},{"internalType":"uint64","name":"borrowCollateralFactor","type":"uint64"},{"internalType":"uint64","name":"liquidateCollateralFactor","type":"uint64"},{"internalType":"uint64","name":"liquidationFactor","type":"uint64"},{"internalType":"uint128","name":"supplyCap","type":"uint128"}],"internalType":"struct CometCore.AssetInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function" }, 17 | ]; 18 | 19 | const myContractAbi = [ 20 | 'function getBorrowCollateralFactor(address asset) public view returns (uint)', 21 | 'function getLiquidateCollateralFactor(address) public view returns (uint)', 22 | ]; 23 | 24 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress, usdcAddress, wbtcAddress; 25 | 26 | const mnemonic = hre.network.config.accounts.mnemonic; 27 | const addresses = []; 28 | const privateKeys = []; 29 | for (let i = 0; i < 20; i++) { 30 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 31 | addresses.push(wallet.address); 32 | privateKeys.push(wallet._signingKey().privateKey); 33 | } 34 | 35 | describe("Find a Compound III asset collateral factors", function () { 36 | before(async () => { 37 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 38 | 39 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 40 | hostname: '127.0.0.1', 41 | port: 8545, 42 | provider: hre.network.provider 43 | }); 44 | 45 | await jsonRpcServer.listen(); 46 | 47 | baseAssetAddress = networks[net].USDC; 48 | usdcAddress = baseAssetAddress; 49 | wbtcAddress = networks[net].WBTC; 50 | cometAddress = networks[net].comet; 51 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 52 | }); 53 | 54 | beforeEach(async () => { 55 | await resetForkedChain(hre, providerUrl, blockNumber); 56 | deployment = await myContractFactory.deploy(cometAddress); 57 | }); 58 | 59 | after(async () => { 60 | await jsonRpcServer.close(); 61 | }); 62 | 63 | it('Finds the collateral and liquidation factors for an asset using JS', async () => { 64 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 65 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 66 | 67 | const borrowCollateralFactor = (await comet.callStatic.getAssetInfoByAddress(wbtcAddress)).borrowCollateralFactor; 68 | const liquidateCollateralFactor = (await comet.callStatic.getAssetInfoByAddress(wbtcAddress)).liquidateCollateralFactor; 69 | 70 | console.log('\tborrowCollateralFactor', +borrowCollateralFactor.toString() / 1e18 * 100); 71 | console.log('\tliquidateCollateralFactor', +liquidateCollateralFactor.toString() / 1e18 * 100); 72 | }); 73 | 74 | it('Finds the collateral and liquidation factors for an asset using Solidity', async () => { 75 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 76 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, provider); 77 | 78 | const borrowCollateralFactor = (await MyContract.callStatic.getBorrowCollateralFactor(wbtcAddress)); 79 | const liquidateCollateralFactor = (await MyContract.callStatic.getLiquidateCollateralFactor(wbtcAddress)); 80 | 81 | console.log('\tSolidity - borrowCollateralFactor', +borrowCollateralFactor.toString() / 1e18 * 100); 82 | console.log('\tSolidity - liquidateCollateralFactor', +liquidateCollateralFactor.toString() / 1e18 * 100); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /examples/get-principal-example.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const cometAbi = [ 14 | 'event Supply(address indexed from, address indexed dst, uint256 amount)', 15 | 'event Withdraw(address indexed src, address indexed to, uint256 amount)', 16 | ]; 17 | 18 | const baseAbi = [ 19 | 'function decimals() returns (uint)' 20 | ]; 21 | 22 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress; 23 | 24 | const mnemonic = hre.network.config.accounts.mnemonic; 25 | const addresses = []; 26 | const privateKeys = []; 27 | for (let i = 0; i < 20; i++) { 28 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 29 | addresses.push(wallet.address); 30 | privateKeys.push(wallet._signingKey().privateKey); 31 | } 32 | 33 | describe("Find an account's Compound III principal base asset supplied", function () { 34 | before(async () => { 35 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 36 | 37 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 38 | hostname: '127.0.0.1', 39 | port: 8545, 40 | provider: hre.network.provider 41 | }); 42 | 43 | await jsonRpcServer.listen(); 44 | 45 | baseAssetAddress = networks[net].USDC; 46 | cometAddress = networks[net].comet; 47 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 48 | }); 49 | 50 | beforeEach(async () => { 51 | await resetForkedChain(hre, providerUrl, blockNumber); 52 | deployment = await myContractFactory.deploy(cometAddress); 53 | }); 54 | 55 | after(async () => { 56 | await jsonRpcServer.close(); 57 | }); 58 | 59 | it('Finds the original supplied amount of base asset using JS', async () => { 60 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 61 | const base = new ethers.Contract(baseAssetAddress, baseAbi, provider); 62 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 63 | 64 | const myAddress = addresses[0]; 65 | const eventFilterSupply = comet.filters.Supply(myAddress); 66 | const eventFilterWithdraw = comet.filters.Withdraw(myAddress); 67 | const eventsSupply = await comet.queryFilter(eventFilterSupply); 68 | const eventsWithdraw = await comet.queryFilter(eventFilterWithdraw); 69 | 70 | let principal = 0; 71 | eventsSupply.forEach((event) => { 72 | principal += +event.args[1].toString(); 73 | }); 74 | 75 | eventsWithdraw.forEach((event) => { 76 | principal -= +event.args[0].toString(); 77 | }); 78 | 79 | const decimals = await base.callStatic.decimals(); 80 | principal = principal / Math.pow(10, +decimals.toString()); 81 | 82 | console.log('\tPrincipal', principal); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /examples/get-supported-assets.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const networks = require('./addresses.json'); 6 | const net = hre.config.cometInstance; 7 | 8 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 9 | const providerUrl = hre.config.networks.hardhat.forking.url; 10 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 11 | 12 | function forEachWithCallback(callback, after) { 13 | const arrayCopy = this; 14 | let index = 0; 15 | const next = () => { 16 | index++; 17 | if (arrayCopy.length > 0) { 18 | callback(arrayCopy.shift(), index, next); 19 | } else { 20 | if (after) after(); 21 | } 22 | } 23 | next(); 24 | } 25 | 26 | Array.prototype.forEachWithCallback = forEachWithCallback; 27 | 28 | const myContractAbi = [ 29 | 'function getAllAssetInfos() public', 30 | `event AssetInfoLog(tuple( 31 | uint8 offset, 32 | address asset, 33 | address priceFeed, 34 | uint64 scale, 35 | uint64 borrowCollateralFactor, 36 | uint64 liquidateCollateralFactor, 37 | uint64 liquidationFactor, 38 | uint128 supplyCap 39 | ))`, 40 | 'event LogUint(string, uint)', 41 | 'event LogAddress(string, address)', 42 | ]; 43 | 44 | const cometAbi = [ 45 | `function getAssetInfo(uint8 i) virtual public view returns ( 46 | tuple( 47 | uint8 offset, 48 | address asset, 49 | address priceFeed, 50 | uint64 scale, 51 | uint64 borrowCollateralFactor, 52 | uint64 liquidateCollateralFactor, 53 | uint64 liquidationFactor, 54 | uint128 supplyCap 55 | ) memory 56 | )`, 57 | 58 | 'function baseBorrowMin() returns (uint)', 59 | 'function baseMinForRewards() returns (uint)', 60 | 'function baseScale() returns (uint)', 61 | 'function baseToken() returns (address)', 62 | 'function baseTokenPriceFeed() returns (address)', 63 | 'function baseTrackingBorrowSpeed() returns (uint)', 64 | 'function baseTrackingSupplySpeed() returns (uint)', 65 | 66 | 'function numAssets() returns (uint)', 67 | ]; 68 | 69 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress; 70 | 71 | const mnemonic = hre.network.config.accounts.mnemonic; 72 | const addresses = []; 73 | const privateKeys = []; 74 | for (let i = 0; i < 20; i++) { 75 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 76 | addresses.push(wallet.address); 77 | privateKeys.push(wallet._signingKey().privateKey); 78 | } 79 | 80 | describe("Finds asset info for an instance of Compound III", function () { 81 | before(async () => { 82 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 83 | 84 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 85 | hostname: '127.0.0.1', 86 | port: 8545, 87 | provider: hre.network.provider 88 | }); 89 | 90 | await jsonRpcServer.listen(); 91 | 92 | cometAddress = networks[net].comet; 93 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 94 | }); 95 | 96 | beforeEach(async () => { 97 | deployment = await myContractFactory.deploy(cometAddress); 98 | }); 99 | 100 | after(async () => { 101 | await jsonRpcServer.close(); 102 | }); 103 | 104 | it('Finds all assets supported by a Comet instance using JS', async () => { 105 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 106 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 107 | 108 | function logCollateralAssetInfos() { 109 | return new Promise(async (resolve, reject) => { 110 | const indexes = []; 111 | const numAssets = +(await comet.callStatic.numAssets()).toString(); 112 | for (let i = 0; i < numAssets; i++) indexes.push(i); 113 | indexes.forEachWithCallback(async (i, x, done) => { 114 | const collateralAssetInfo = await comet.callStatic.getAssetInfo(i); 115 | console.log('Collateral Asset Info', i, collateralAssetInfo); 116 | done(); 117 | }, resolve); 118 | }); 119 | } 120 | 121 | await logCollateralAssetInfos(); 122 | 123 | console.log('Base asset info:'); 124 | console.log('baseBorrowMin \t\t', (await comet.callStatic.baseBorrowMin()).toString()); 125 | console.log('baseMinForRewards \t', (await comet.callStatic.baseMinForRewards()).toString()); 126 | console.log('baseScale \t\t', (await comet.callStatic.baseScale()).toString()); 127 | console.log('baseToken \t\t', (await comet.callStatic.baseToken()).toString()); 128 | console.log('baseTokenPriceFeed \t', (await comet.callStatic.baseTokenPriceFeed()).toString()); 129 | console.log('baseTrackingBorrowSpeed ', (await comet.callStatic.baseTrackingBorrowSpeed()).toString()); 130 | console.log('baseTrackingSupplySpeed ', (await comet.callStatic.baseTrackingSupplySpeed()).toString()); 131 | }); 132 | 133 | it('Finds all assets supported by a Comet instance using Solidity', async () => { 134 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 135 | const signer = provider.getSigner(addresses[0]); 136 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, signer); 137 | 138 | let trx = await MyContract.getAllAssetInfos(); 139 | const receipt = await trx.wait(1); 140 | console.log('\tSolidity:'); 141 | 142 | receipt.events.forEach((event) => { 143 | console.log(event.args); 144 | }); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /examples/interest-earned-example.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const cometAbi = [ 14 | 'event Supply(address indexed from, address indexed dst, uint256 amount)', 15 | 'function balanceOf(address account) returns (uint256)', 16 | ]; 17 | 18 | const baseAbi = [ 19 | 'function decimals() returns (uint)' 20 | ]; 21 | 22 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress; 23 | 24 | const mnemonic = hre.network.config.accounts.mnemonic; 25 | const addresses = []; 26 | const privateKeys = []; 27 | for (let i = 0; i < 20; i++) { 28 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 29 | addresses.push(wallet.address); 30 | privateKeys.push(wallet._signingKey().privateKey); 31 | } 32 | 33 | describe("Find an account's Compound III base asset interest earned", function () { 34 | before(async () => { 35 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 36 | 37 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 38 | hostname: '127.0.0.1', 39 | port: 8545, 40 | provider: hre.network.provider 41 | }); 42 | 43 | await jsonRpcServer.listen(); 44 | 45 | baseAssetAddress = networks[net].USDC; 46 | cometAddress = networks[net].comet; 47 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 48 | }); 49 | 50 | beforeEach(async () => { 51 | await resetForkedChain(hre, providerUrl, blockNumber); 52 | deployment = await myContractFactory.deploy(cometAddress); 53 | }); 54 | 55 | after(async () => { 56 | await jsonRpcServer.close(); 57 | }); 58 | 59 | // This example only works for accounts that have not ever borrowed 60 | it('Finds the interest earned of base asset using JS', async () => { 61 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 62 | const base = new ethers.Contract(baseAssetAddress, baseAbi, provider); 63 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 64 | 65 | const myAddress = addresses[0]; 66 | 67 | const balance = await comet.callStatic.balanceOf(myAddress); 68 | 69 | const eventFilterSupply = comet.filters.Supply(myAddress); 70 | const eventsSupply = await comet.queryFilter(eventFilterSupply); 71 | 72 | let principal = 0; 73 | eventsSupply.forEach((event) => { 74 | principal += +event.args[1].toString(); 75 | }); 76 | 77 | const decimals = await base.callStatic.decimals(); 78 | const interest = (balance - principal) / Math.pow(10, +decimals.toString()); 79 | 80 | console.log('\tInterest', interest); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /examples/repay-full-borrow-example.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const cometAbi = [ 14 | 'event Supply(address indexed from, address indexed dst, uint256 amount)', 15 | 'function supply(address asset, uint amount)', 16 | 'function withdraw(address asset, uint amount)', 17 | 'function balanceOf(address account) returns (uint256)', 18 | 'function borrowBalanceOf(address account) returns (uint256)', 19 | 'function collateralBalanceOf(address account, address asset) external view returns (uint128)', 20 | ]; 21 | 22 | const wethAbi = [ 23 | 'function deposit() payable', 24 | 'function balanceOf(address) returns (uint)', 25 | 'function approve(address, uint) returns (bool)', 26 | 'function transfer(address, uint)', 27 | ]; 28 | 29 | const stdErc20Abi = [ 30 | 'function approve(address, uint) returns (bool)', 31 | 'function transfer(address, uint)', 32 | ]; 33 | 34 | const myContractAbi = [ 35 | 'function supply(address asset, uint amount) public', 36 | 'function withdraw(address asset, uint amount) public', 37 | 'function repayFullBorrow(address baseAsset) public', 38 | ]; 39 | 40 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress, wethAddress; 41 | 42 | const mnemonic = hre.network.config.accounts.mnemonic; 43 | const addresses = []; 44 | const privateKeys = []; 45 | for (let i = 0; i < 20; i++) { 46 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 47 | addresses.push(wallet.address); 48 | privateKeys.push(wallet._signingKey().privateKey); 49 | } 50 | 51 | describe("Repay an entire Compound III account's borrow", function () { 52 | before(async () => { 53 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 54 | 55 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 56 | hostname: '127.0.0.1', 57 | port: 8545, 58 | provider: hre.network.provider 59 | }); 60 | 61 | await jsonRpcServer.listen(); 62 | 63 | baseAssetAddress = networks[net].USDC; 64 | usdcAddress = baseAssetAddress; 65 | cometAddress = networks[net].comet; 66 | wethAddress = networks[net].WETH; 67 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 68 | }); 69 | 70 | beforeEach(async () => { 71 | await resetForkedChain(hre, providerUrl, blockNumber); 72 | deployment = await myContractFactory.deploy(cometAddress); 73 | }); 74 | 75 | after(async () => { 76 | await jsonRpcServer.close(); 77 | }); 78 | 79 | it('Repays an entire borrow without missing latest block interest using JS', async () => { 80 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 81 | const signer = provider.getSigner(addresses[0]); 82 | const comet = new ethers.Contract(cometAddress, cometAbi, signer); 83 | const weth = new ethers.Contract(wethAddress, wethAbi, signer); 84 | const usdc = new ethers.Contract(usdcAddress, stdErc20Abi, signer); 85 | const baseAssetMantissa = 1e6; // USDC has 6 decimal places 86 | 87 | let tx = await weth.deposit({ value: ethers.utils.parseEther('10') }); 88 | await tx.wait(1); 89 | 90 | console.log('\tApproving Comet to move WETH collateral...'); 91 | tx = await weth.approve(cometAddress, ethers.constants.MaxUint256); 92 | await tx.wait(1); 93 | 94 | console.log('\tSending initial supply to Compound...'); 95 | tx = await comet.supply(wethAddress, ethers.utils.parseEther('10')); 96 | await tx.wait(1); 97 | 98 | // Accounts cannot hold a borrow smaller than baseBorrowMin (100 USDC). 99 | const borrowSize = 1000; 100 | console.log('\tExecuting initial borrow of the base asset from Compound...'); 101 | console.log('\tBorrow size:', borrowSize); 102 | 103 | // Do borrow 104 | tx = await comet.withdraw(usdcAddress, (borrowSize * baseAssetMantissa).toString()); 105 | await tx.wait(1); 106 | 107 | let borrowBalance = await comet.callStatic.borrowBalanceOf(addresses[0]); 108 | console.log('\tBorrow Balance initial', +borrowBalance.toString() / baseAssetMantissa); 109 | 110 | // accrue some interest 111 | console.log('\tFast forwarding 100 blocks to accrue some borrower interest...'); 112 | await advanceBlockHeight(100); 113 | 114 | borrowBalance = await comet.callStatic.borrowBalanceOf(addresses[0]); 115 | console.log('\tBorrow Balance after some interest accrued', +borrowBalance.toString() / baseAssetMantissa); 116 | 117 | // For example purposes, get extra USDC so we can pay off the 118 | // original borrow plus the accrued borrower interest 119 | await seedWithBaseToken(addresses[0], 5); 120 | 121 | tx = await usdc.approve(cometAddress, ethers.constants.MaxUint256); 122 | await tx.wait(1); 123 | 124 | console.log('\tRepaying the entire borrow...'); 125 | tx = await comet.supply(usdcAddress, ethers.constants.MaxUint256); 126 | await tx.wait(1); 127 | 128 | borrowBalance = await comet.callStatic.borrowBalanceOf(addresses[0]); 129 | console.log('\tBorrow Balance after full repayment', +borrowBalance.toString() / baseAssetMantissa); 130 | }); 131 | 132 | it('Repays an entire borrow without missing latest block interest using Solidity', async () => { 133 | const me = addresses[0]; 134 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 135 | const signer = provider.getSigner(me); 136 | const comet = new ethers.Contract(cometAddress, cometAbi, signer); 137 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, signer); 138 | const weth = new ethers.Contract(wethAddress, wethAbi, signer); 139 | const wethMantissa = 1e18; // WETH and ETH have 18 decimal places 140 | const usdc = new ethers.Contract(baseAssetAddress, stdErc20Abi, signer); 141 | const baseAssetMantissa = 1e6; // USDC has 6 decimal places 142 | 143 | let tx = await weth.deposit({ value: ethers.utils.parseEther('10') }); 144 | await tx.wait(1); 145 | 146 | console.log('\tTransferring WETH to MyContract to use as collateral...'); 147 | tx = await weth.transfer(MyContract.address, ethers.utils.parseEther('10')); 148 | await tx.wait(1); 149 | 150 | console.log('\tSending initial supply to Compound...'); 151 | tx = await MyContract.supply(wethAddress, ethers.utils.parseEther('10')); 152 | await tx.wait(1); 153 | 154 | // Accounts cannot hold a borrow smaller than baseBorrowMin (100 USDC). 155 | const borrowSize = 1000; 156 | console.log('\tExecuting initial borrow of the base asset from Compound...'); 157 | console.log('\tBorrow size:', borrowSize); 158 | 159 | // Do borrow 160 | tx = await MyContract.withdraw(usdcAddress, (borrowSize * baseAssetMantissa).toString()); 161 | await tx.wait(1); 162 | 163 | // accrue some interest 164 | console.log('\tFast forwarding 100 blocks to accrue some borrower interest...'); 165 | await advanceBlockHeight(100); 166 | 167 | borrowBalance = await comet.callStatic.borrowBalanceOf(MyContract.address); 168 | console.log('\tBorrow Balance after some interest accrued', +borrowBalance.toString() / baseAssetMantissa); 169 | 170 | // For example purposes, get extra USDC so we can pay off the 171 | // original borrow plus the accrued borrower interest 172 | await seedWithBaseToken(MyContract.address, 5); 173 | 174 | console.log('\tRepaying the entire borrow...'); 175 | tx = await MyContract.repayFullBorrow(usdcAddress); 176 | await tx.wait(1); 177 | 178 | borrowBalance = await comet.callStatic.borrowBalanceOf(MyContract.address); 179 | console.log('\tBorrow Balance after full repayment', +borrowBalance.toString() / baseAssetMantissa); 180 | }); 181 | }); 182 | 183 | async function advanceBlockHeight(blocks) { 184 | const txns = []; 185 | for (let i = 0; i < blocks; i++) { 186 | txns.push(hre.network.provider.send('evm_mine')); 187 | } 188 | await Promise.all(txns); 189 | } 190 | 191 | // Test account index 9 uses Comet to borrow and then seed the toAddress with tokens 192 | async function seedWithBaseToken(toAddress, amt) { 193 | const baseTokenDecimals = 6; // USDC 194 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 195 | const signer = provider.getSigner(addresses[9]); 196 | const comet = new ethers.Contract(cometAddress, cometAbi, signer); 197 | const weth = new ethers.Contract(wethAddress, wethAbi, signer); 198 | const usdc = new ethers.Contract(usdcAddress, stdErc20Abi, signer); 199 | 200 | let tx = await weth.deposit({ value: ethers.utils.parseEther('10') }); 201 | await tx.wait(1); 202 | 203 | tx = await weth.approve(cometAddress, ethers.constants.MaxUint256); 204 | await tx.wait(1); 205 | 206 | tx = await comet.supply(wethAddress, ethers.utils.parseEther('10')); 207 | await tx.wait(1); 208 | 209 | // baseBorrowMin is 1000 USDC 210 | tx = await comet.withdraw(usdcAddress, (1000 * 1e6).toString()); 211 | await tx.wait(1); 212 | 213 | // transfer from this account to the main test account (0th) 214 | tx = await usdc.transfer(toAddress, (amt * 1e6).toString()); 215 | await tx.wait(1); 216 | 217 | return; 218 | } -------------------------------------------------------------------------------- /examples/supply-withdraw-example.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const myContractAbi = [ 14 | 'function supply(address asset, uint amount) public', 15 | 'function withdraw(address asset, uint amount) public', 16 | ]; 17 | 18 | const cometAbi = [ 19 | 'event Supply(address indexed from, address indexed dst, uint256 amount)', 20 | 'function supply(address asset, uint amount)', 21 | 'function withdraw(address asset, uint amount)', 22 | 'function balanceOf(address account) returns (uint256)', 23 | 'function borrowBalanceOf(address account) returns (uint256)', 24 | 'function collateralBalanceOf(address account, address asset) external view returns (uint128)', 25 | ]; 26 | 27 | const wethAbi = [ 28 | 'function deposit() payable', 29 | 'function balanceOf(address) returns (uint)', 30 | 'function approve(address, uint) returns (bool)', 31 | 'function transfer(address, uint)', 32 | ]; 33 | 34 | const stdErc20Abi = [ 35 | 'function approve(address, uint) returns (bool)', 36 | 'function transfer(address, uint)', 37 | ]; 38 | 39 | let jsonRpcServer, deployment, cometAddress, myContractFactory, baseAssetAddress, wethAddress; 40 | 41 | const mnemonic = hre.network.config.accounts.mnemonic; 42 | const addresses = []; 43 | const privateKeys = []; 44 | for (let i = 0; i < 20; i++) { 45 | const wallet = new ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`); 46 | addresses.push(wallet.address); 47 | privateKeys.push(wallet._signingKey().privateKey); 48 | } 49 | 50 | describe("Compound III Supply Examples", function () { 51 | before(async () => { 52 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 53 | 54 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 55 | hostname: '127.0.0.1', 56 | port: 8545, 57 | provider: hre.network.provider 58 | }); 59 | 60 | await jsonRpcServer.listen(); 61 | 62 | baseAssetAddress = networks[net].USDC; 63 | usdcAddress = baseAssetAddress; 64 | cometAddress = networks[net].comet; 65 | wethAddress = networks[net].WETH; 66 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 67 | }); 68 | 69 | beforeEach(async () => { 70 | await resetForkedChain(hre, providerUrl, blockNumber); 71 | deployment = await myContractFactory.deploy(cometAddress); 72 | }); 73 | 74 | after(async () => { 75 | await jsonRpcServer.close(); 76 | }); 77 | 78 | it('Supplies collateral and then withdraws it using JS', async () => { 79 | const me = addresses[0]; 80 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 81 | const signer = provider.getSigner(me); 82 | const comet = new ethers.Contract(cometAddress, cometAbi, signer); 83 | const weth = new ethers.Contract(wethAddress, wethAbi, signer); 84 | const wethMantissa = 1e18; // WETH and ETH have 18 decimal places 85 | 86 | let tx = await weth.deposit({ value: ethers.utils.parseEther('10') }); 87 | await tx.wait(1); 88 | 89 | console.log('\tApproving Comet to move WETH collateral...'); 90 | tx = await weth.approve(cometAddress, ethers.constants.MaxUint256); 91 | await tx.wait(1); 92 | 93 | console.log('\tSending initial supply to Compound...'); 94 | tx = await comet.supply(wethAddress, ethers.utils.parseEther('10')); 95 | await tx.wait(1); 96 | 97 | let collateralBalance = await comet.callStatic.collateralBalanceOf(me, wethAddress); 98 | console.log('\tMy current WETH collateral balance:', +collateralBalance.toString() / wethMantissa); 99 | 100 | console.log('\tWithdrawing collateral from Compound...'); 101 | tx = await comet.withdraw(wethAddress, ethers.utils.parseEther('10')); 102 | await tx.wait(1); 103 | 104 | collateralBalance = await comet.callStatic.collateralBalanceOf(me, wethAddress); 105 | console.log('\tMy current WETH collateral balance:', +collateralBalance.toString() / wethMantissa); 106 | }); 107 | 108 | it('Supplies collateral and then withdraws it using Solidity', async () => { 109 | const me = addresses[0]; 110 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 111 | const signer = provider.getSigner(me); 112 | const comet = new ethers.Contract(cometAddress, cometAbi, signer); 113 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, signer); 114 | const weth = new ethers.Contract(wethAddress, wethAbi, signer); 115 | const wethMantissa = 1e18; // WETH and ETH have 18 decimal places 116 | 117 | let tx = await weth.deposit({ value: ethers.utils.parseEther('10') }); 118 | await tx.wait(1); 119 | 120 | console.log('\tMoving WETH from my wallet to MyContract...'); 121 | tx = await weth.transfer(MyContract.address, ethers.utils.parseEther('10')); 122 | await tx.wait(1); 123 | 124 | console.log('\tSupplying WETH from MyContract to Compound...'); 125 | tx = await MyContract.supply(wethAddress, ethers.utils.parseEther('3')); 126 | await tx.wait(1); 127 | 128 | // let wethBalance = await weth.callStatic.balanceOf(MyContract.address); 129 | // console.log("\tMyContract's current WETH balance:", +wethBalance.toString() / wethMantissa); 130 | 131 | // let collateralBalance = await comet.callStatic.collateralBalanceOf(MyContract.address, wethAddress); 132 | // console.log("\tMyContract's current WETH collateral balance:", +collateralBalance.toString() / wethMantissa); 133 | 134 | console.log('\tWithdrawing WETH collateral from Compound to MyContract...'); 135 | tx = await MyContract.withdraw(wethAddress, ethers.utils.parseEther('3')); 136 | await tx.wait(1); 137 | 138 | // wethBalance = await weth.callStatic.balanceOf(MyContract.address); 139 | // console.log("\tMyContract's current WETH balance:", +wethBalance.toString() / wethMantissa); 140 | 141 | // collateralBalance = await comet.callStatic.collateralBalanceOf(MyContract.address, wethAddress); 142 | // console.log("\tMyContract's current WETH collateral balance:", +collateralBalance.toString() / wethMantissa); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /examples/tvl-example.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { TASK_NODE_CREATE_SERVER } = require('hardhat/builtin-tasks/task-names'); 3 | const hre = require('hardhat'); 4 | const ethers = require('ethers'); 5 | const { resetForkedChain } = require('./common.js'); 6 | const networks = require('./addresses.json'); 7 | const net = hre.config.cometInstance; 8 | 9 | const jsonRpcUrl = 'http://127.0.0.1:8545'; 10 | const providerUrl = hre.config.networks.hardhat.forking.url; 11 | const blockNumber = hre.config.networks.hardhat.forking.blockNumber; 12 | 13 | const myContractAbi = [ 14 | 'function getTvl() public view returns (uint)', 15 | ]; 16 | 17 | const cometAbi = [ 18 | 'function numAssets() returns (uint8)', 19 | 'function getAssetInfo(uint8 i) public view returns (tuple(uint8 offset, address asset, address priceFeed, uint64 scale, uint64 borrowCollateralFactor, uint64 liquidateCollateralFactor, uint64 liquidationFactor, uint128 supplyCap) memory)', 20 | 'function totalsCollateral(address) returns (uint128 totalSupplyAsset, uint128 _reserved)', 21 | 'function totalSupply() returns (uint)', 22 | 'function decimals() returns (uint8)', 23 | 'function baseTokenPriceFeed() returns (address)', 24 | 'function getPrice(address) public view returns (uint128)', 25 | ]; 26 | 27 | let jsonRpcServer, deployment, cometAddress, myContractFactory; 28 | 29 | describe('Calculating the Compound III APRs', function () { 30 | before(async () => { 31 | console.log('\n Running a hardhat local evm fork of a public net...\n'); 32 | 33 | jsonRpcServer = await hre.run(TASK_NODE_CREATE_SERVER, { 34 | hostname: '127.0.0.1', 35 | port: 8545, 36 | provider: hre.network.provider 37 | }); 38 | 39 | await jsonRpcServer.listen(); 40 | 41 | cometAddress = networks[net].comet; 42 | myContractFactory = await hre.ethers.getContractFactory('MyContract'); 43 | }); 44 | 45 | beforeEach(async () => { 46 | await resetForkedChain(hre, providerUrl, blockNumber); 47 | deployment = await myContractFactory.deploy(cometAddress); 48 | }); 49 | 50 | after(async () => { 51 | await jsonRpcServer.close(); 52 | }); 53 | 54 | it('Calculates the TVL in USD of Compound III using JS', async () => { 55 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 56 | const comet = new ethers.Contract(cometAddress, cometAbi, provider); 57 | 58 | const numAssets = await comet.callStatic.numAssets(); 59 | 60 | const promisesAssets = []; 61 | for (let i = 0; i < numAssets; i++) { 62 | promisesAssets.push(comet.callStatic.getAssetInfo(i)); 63 | } 64 | 65 | const assetInfos = await Promise.all(promisesAssets); 66 | 67 | const promisesPrices = []; 68 | const promisesLocked = []; 69 | for (let i = 0; i < numAssets; i++) { 70 | const { asset, priceFeed } = assetInfos[i]; 71 | promisesLocked.push(comet.callStatic.totalsCollateral(asset)); 72 | promisesPrices.push(comet.callStatic.getPrice(priceFeed)); 73 | } 74 | 75 | const prices = await Promise.all(promisesPrices); 76 | const lockedAmounts = await Promise.all(promisesLocked); 77 | 78 | const baseScale = Math.pow(10, +(await comet.callStatic.decimals()).toString()); 79 | const basePf = await comet.callStatic.baseTokenPriceFeed(); 80 | const basePrice = +(await comet.callStatic.getPrice(basePf)).toString() / 1e8; // price feed 8 decimals 81 | const totalSupplyBase = +(await comet.callStatic.totalSupply()).toString() / baseScale; 82 | 83 | let tvlUsd = totalSupplyBase * basePrice; 84 | 85 | for (let i = 0; i < numAssets; i++) { 86 | const price = +prices[i].toString() / 1e8; // price feed 8 decimals 87 | const locked = +lockedAmounts[i].totalSupplyAsset.toString() / +assetInfos[i].scale.toString(); 88 | tvlUsd += price * locked; 89 | } 90 | 91 | console.log('\tJS - Compound III TVL in USD', tvlUsd); 92 | 93 | }); 94 | 95 | it('Calculates the TVL in USD of Compound III using Solidity', async () => { 96 | const provider = new ethers.providers.JsonRpcProvider(jsonRpcUrl); 97 | const MyContract = new ethers.Contract(deployment.address, myContractAbi, provider); 98 | 99 | const tvlUsd = +(await MyContract.callStatic.getTvl()).toString() / 1e8; // price feed 8 decimals 100 | 101 | console.log('\tSolidity - Compound III TVL in USD', tvlUsd); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers'); 2 | 3 | // Choose the Comet instance to run tests against using the `connections` object 4 | const cometInstance = 'usdc-polygon'; 5 | 6 | // Optionally, you can hardcode provider URLs here 7 | const connections = { 8 | 'usdc-mainnet': { 9 | providerUrl: process.env.MAINNET_PROVIDER_URL, 10 | // blockNumber: 16192000, // 2022-12-15T18:51:47.000Z 11 | blockNumber: 17330000, // 2023-05-24T03:41:11.000Z 12 | chainId: 1, 13 | }, 14 | 'usdc-goerli': { 15 | providerUrl: process.env.GOERLI_PROVIDER_URL, 16 | blockNumber: 8141000, // 2022-12-15T19:00:48.000Z 17 | chainId: 5, 18 | }, 19 | 'usdc-polygon': { 20 | providerUrl: process.env.POLYGON_PROVIDER_URL, 21 | blockNumber: 48949630, // 2023-10-20T16:23:02.000Z 22 | chainId: 137, 23 | } 24 | }; 25 | 26 | const { providerUrl, blockNumber, chainId } = connections[cometInstance]; 27 | 28 | if (!providerUrl) { 29 | console.error('Cannot connect to the blockchain.'); 30 | console.error('Add a provider URL in the hardhat.config.js file.'); 31 | process.exit(1); 32 | } 33 | 34 | // Do not use this mnemonic outside of localhost tests! 35 | const mnemonic = 'romance zebra roof insect stem water kiwi park acquire domain gossip second'; 36 | 37 | if (!providerUrl) { 38 | console.error('Missing JSON RPC provider URL as environment variable. See hardhat.config.js.'); 39 | process.exit(1); 40 | } 41 | 42 | module.exports = { 43 | cometInstance, // this tells the test scripts which addresses to use 44 | testProviderUrl: providerUrl, 45 | testBlockNumber: blockNumber, 46 | solidity: { 47 | version: '0.8.11', 48 | settings: { 49 | optimizer: { 50 | enabled: true, 51 | runs: 1000 52 | } 53 | } 54 | }, 55 | networks: { 56 | hardhat: { 57 | chainId, 58 | forking: { 59 | url: providerUrl, 60 | blockNumber, 61 | }, 62 | gasPrice: 0, 63 | initialBaseFeePerGas: 0, 64 | loggingEnabled: false, 65 | accounts: { 66 | mnemonic 67 | }, 68 | }, 69 | }, 70 | mocha: { 71 | timeout: 60000 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound-developer-faq", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx hardhat compile", 8 | "test": "npm run build && npx mocha ./examples/*.js --timeout 60000" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@compound-finance/compound-js": "^0.6.0", 15 | "@nomiclabs/hardhat-ethers": "^2.2.3", 16 | "hardhat": "^2.14.0", 17 | "mocha": "^10.2.0" 18 | } 19 | } 20 | --------------------------------------------------------------------------------