├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .githooks └── pre-commit ├── .github └── workflows │ ├── deploy-market.yaml │ ├── enact-migration.yaml │ ├── prepare-migration.yaml │ ├── run-contract-linter.yaml │ ├── run-coverage.yaml │ ├── run-eslint.yaml │ ├── run-gas-profiler.yaml │ ├── run-scenarios.yaml │ ├── run-slither.yaml │ └── run-unit-tests.yaml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── LICENSE ├── MIGRATIONS.md ├── README.md ├── SCENARIO.md ├── SPEC.md ├── SPEC.pdf ├── contracts ├── Bulker.sol ├── Comet.sol ├── CometConfiguration.sol ├── CometCore.sol ├── CometExt.sol ├── CometExtInterface.sol ├── CometFactory.sol ├── CometInterface.sol ├── CometMainInterface.sol ├── CometMath.sol ├── CometProxyAdmin.sol ├── CometRewards.sol ├── CometStorage.sol ├── Configurator.sol ├── ConfiguratorProxy.sol ├── ConfiguratorStorage.sol ├── ERC20.sol ├── IComp.sol ├── IGovernorBravo.sol ├── IProxy.sol ├── ITimelock.sol ├── IWETH9.sol ├── bridges │ ├── BaseBridgeReceiver.sol │ ├── polygon │ │ └── PolygonBridgeReceiver.sol │ ├── test │ │ └── BaseBridgeReceiverHarness.sol │ └── vendor │ │ ├── fx-portal │ │ └── contracts │ │ │ └── FxChild.sol │ │ └── manifest.json ├── liquidator │ ├── Liquidator.sol │ ├── README.md │ └── vendor │ │ ├── @openzeppelin │ │ └── contracts │ │ │ └── token │ │ │ └── ERC20 │ │ │ └── IERC20.sol │ │ ├── @uniswap │ │ ├── v3-core │ │ │ └── contracts │ │ │ │ └── interfaces │ │ │ │ ├── IUniswapV3Pool.sol │ │ │ │ ├── callback │ │ │ │ ├── IUniswapV3FlashCallback.sol │ │ │ │ └── IUniswapV3SwapCallback.sol │ │ │ │ └── pool │ │ │ │ ├── IUniswapV3PoolActions.sol │ │ │ │ ├── IUniswapV3PoolDerivedState.sol │ │ │ │ ├── IUniswapV3PoolEvents.sol │ │ │ │ ├── IUniswapV3PoolImmutables.sol │ │ │ │ ├── IUniswapV3PoolOwnerActions.sol │ │ │ │ └── IUniswapV3PoolState.sol │ │ └── v3-periphery │ │ │ └── contracts │ │ │ ├── base │ │ │ ├── PeripheryImmutableState.sol │ │ │ └── PeripheryPayments.sol │ │ │ ├── interfaces │ │ │ ├── IPeripheryImmutableState.sol │ │ │ ├── IPeripheryPayments.sol │ │ │ ├── ISwapRouter.sol │ │ │ └── external │ │ │ │ └── IWETH9.sol │ │ │ └── libraries │ │ │ ├── CallbackValidation.sol │ │ │ ├── PoolAddress.sol │ │ │ └── TransferHelper.sol │ │ └── manifest.json ├── test │ ├── CometHarness.sol │ ├── CometHarnessInterface.sol │ ├── CometModified.sol │ ├── CometModifiedFactory.sol │ ├── Dog.sol │ ├── EvilToken.sol │ ├── FaucetToken.sol │ ├── FaucetWETH.sol │ ├── Fauceteer.sol │ ├── GovernorSimple.sol │ ├── SimplePriceFeed.sol │ └── SimpleTimelock.sol └── vendor │ ├── @chainlink │ └── contracts │ │ └── src │ │ └── v0.8 │ │ └── interfaces │ │ └── AggregatorV3Interface.sol │ ├── Timelock.sol │ ├── access │ └── Ownable.sol │ ├── canonical-weth │ └── contracts │ │ └── WETH9.sol │ ├── interfaces │ └── draft-IERC1822.sol │ ├── manifest.json │ ├── proxy │ ├── ERC1967 │ │ ├── ERC1967Proxy.sol │ │ └── ERC1967Upgrade.sol │ ├── Proxy.sol │ ├── beacon │ │ └── IBeacon.sol │ └── transparent │ │ ├── ProxyAdmin.sol │ │ └── TransparentUpgradeableProxy.sol │ └── utils │ ├── Address.sol │ ├── Context.sol │ └── StorageSlot.sol ├── deployments ├── fuji │ └── usdc │ │ ├── configuration.json │ │ ├── deploy.ts │ │ └── roots.json ├── goerli │ └── usdc │ │ ├── configuration.json │ │ ├── deploy.ts │ │ ├── relations.ts │ │ └── roots.json ├── hardhat │ └── dai │ │ ├── configuration.json │ │ └── deploy.ts ├── kovan │ └── usdc │ │ ├── configuration.json │ │ ├── deploy.ts │ │ └── roots.json ├── mainnet │ └── usdc │ │ ├── configuration.json │ │ ├── deploy.ts │ │ ├── migrations │ │ ├── 1659582050_raise_supply_caps_and_seed_reserves.ts │ │ ├── 1661899622_rewards.ts │ │ └── 1663794398_bump_supply_caps.ts │ │ └── roots.json ├── mumbai │ └── usdc │ │ ├── configuration.json │ │ ├── deploy.ts │ │ └── relations.ts └── relations.ts ├── diagrams ├── configurator_diagram.uml └── inheritance_diagram.uml ├── docs └── README.md ├── hardhat.config.ts ├── index.ts ├── package.json ├── plugins ├── deployment_manager │ ├── Aliases.ts │ ├── Cache.ts │ ├── ContractMap.ts │ ├── Deploy.ts │ ├── DeploymentManager.ts │ ├── Enacted.ts │ ├── Import.ts │ ├── ManualVerify.ts │ ├── Migration.ts │ ├── MigrationTemplate.ts │ ├── NonceManager.ts │ ├── RelationConfig.ts │ ├── Roots.ts │ ├── Spider.ts │ ├── Types.ts │ ├── Utils.ts │ ├── Verify.ts │ ├── VerifyArgs.ts │ ├── index.ts │ ├── test │ │ ├── AliasesTest.ts │ │ ├── CacheTest.ts │ │ ├── ContractMapTest.ts │ │ ├── DeployHelpers.ts │ │ ├── DeployTest.ts │ │ ├── DeploymentManagerTest.ts │ │ ├── EnactedTest.ts │ │ ├── ImportTest.ts │ │ ├── MigrationTemplateTest.ts │ │ ├── MigrationTest.ts │ │ ├── RelationConfigTest.ts │ │ ├── RootsTest.ts │ │ ├── SolcList.json │ │ ├── SpiderTest.ts │ │ ├── TestHelpers.ts │ │ ├── VerifyArgsTest.ts │ │ ├── VerifyTest.ts │ │ └── migration.ts │ └── type-extensions.ts ├── import │ ├── etherscan.ts │ └── import.ts └── scenario │ ├── Loader.ts │ ├── Runner.ts │ ├── Scenario.ts │ ├── Stack.ts │ ├── World.ts │ ├── index.ts │ ├── type-extensions.ts │ ├── types.ts │ ├── utils │ ├── ERC20.ts │ ├── TokenSourcer.ts │ ├── hreForBase.ts │ └── index.ts │ └── worker │ ├── BootstrapWorker.js │ ├── Config.ts │ ├── HardhatContext.ts │ ├── Parent.ts │ ├── Report.ts │ ├── SimpleWorker.ts │ └── Worker.ts ├── scenario ├── .gitkeep ├── AllowBySigScenario.ts ├── AllowScenario.ts ├── ApproveThisScenario.ts ├── BulkerScenario.ts ├── CometScenario.ts ├── ConfiguratorScenario.ts ├── ConstraintScenario.ts ├── GovernanceScenario.ts ├── InterestRateScenario.ts ├── LiquidationScenario.ts ├── PauseGuardianScenario.ts ├── RewardsScenario.ts ├── SupplyScenario.ts ├── TransferScenario.ts ├── WithdrawReservesScenario.ts ├── WithdrawScenario.ts ├── constraints │ ├── CometBalanceConstraint.ts │ ├── FilterConstraint.ts │ ├── Fuzzing.ts │ ├── MigrationConstraint.ts │ ├── ModernConstraint.ts │ ├── PauseConstraint.ts │ ├── ProposalConstraint.ts │ ├── Requirements.ts │ ├── TokenBalanceConstraint.ts │ ├── UtilizationConstraint.ts │ └── index.ts ├── context │ ├── Address.ts │ ├── CometActor.ts │ ├── CometAsset.ts │ ├── CometContext.ts │ └── Gov.ts └── utils │ ├── hreUtils.ts │ ├── index.ts │ ├── relayMessage.ts │ └── relayMumbaiMessage.ts ├── scripts ├── build-spec.js └── liquidation_bot │ ├── deploy.ts │ ├── index.ts │ └── liquidateUnderwaterBorrowers.ts ├── src └── deploy │ ├── Network.ts │ ├── NetworkConfiguration.ts │ └── index.ts ├── tasks ├── deployment_manager │ └── task.ts ├── scenario │ └── task.ts └── spider │ └── task.ts ├── test ├── absorb-test.ts ├── accrue-test.ts ├── allow-by-sig-test.ts ├── allow-test.ts ├── approve-this-test.ts ├── asset-info-test.ts ├── balance-test.ts ├── base-tracking-accrued-test.ts ├── bridges │ └── base-bridge-receiver-test.ts ├── bulker-test.ts ├── buy-collateral-test.ts ├── comet-ext-test.ts ├── configurator-test.ts ├── constructor-test.ts ├── erc20-test.ts ├── fauceteer-test.ts ├── governor-simple-test.ts ├── helpers.ts ├── interest-rate-test.ts ├── is-borrow-collateralized-test.ts ├── is-liquidatable-test.ts ├── liquidation │ ├── addresses.ts │ ├── comp-abi.ts │ ├── dai-abi.ts │ ├── link-abi.ts │ ├── liquidation-bot-script.ts │ ├── liquidation-bot-test.ts │ ├── makeLiquidatableProtocol.ts │ ├── uni-abi.ts │ ├── usdc-abi.ts │ ├── wbtc-abi.ts │ └── weth-abi.ts ├── pause-guardian-test.ts ├── price-feed-test.ts ├── quote-collateral-test.ts ├── reserves-test.ts ├── rewards-test.ts ├── sanity-test.ts ├── scen-test.ts ├── supply-test.ts ├── tracking-index-bounds-test.ts ├── transfer-test.ts ├── update-assets-in-test.ts ├── withdraw-reserves-test.ts └── withdraw-test.ts ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | plugins/scenario/worker/BootstrapWorker.js 2 | plugins/deployment_manager/test/SolcList.json 3 | scripts/build-spec.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true 5 | }, 6 | 'extends': [ 7 | 'eslint:recommended', 8 | 'plugin:@typescript-eslint/recommended' 9 | ], 10 | 'parser': '@typescript-eslint/parser', 11 | 'parserOptions': { 12 | 'ecmaVersion': 'latest', 13 | 'sourceType': 'module' 14 | }, 15 | 'plugins': [ 16 | '@typescript-eslint' 17 | ], 18 | 'rules': { 19 | 'indent': [ 20 | 'error', 21 | 2, 22 | { 23 | 'SwitchCase': 1 24 | } 25 | ], 26 | 'linebreak-style': [ 27 | 'error', 28 | 'unix' 29 | ], 30 | 'no-constant-condition': ['error', { checkLoops: false }], 31 | 'no-prototype-builtins': 'off', 32 | 'no-unused-vars': 'off', 33 | 'prefer-const': 'off', 34 | 'prefer-spread': 'off', 35 | // NB: disabling error for this check, as at is buggy 36 | 'quotes': [ 37 | 'warn', 38 | 'single', 39 | { 40 | 'avoidEscape': true, 41 | 'allowTemplateLiterals': true 42 | } 43 | ], 44 | 'semi': [ 45 | 'error', 46 | 'always' 47 | ], 48 | '@typescript-eslint/member-delimiter-style': [ 49 | 'error', 50 | { 51 | "multiline": { 52 | "delimiter": "semi", 53 | "requireLast": true 54 | }, 55 | "singleline": { 56 | "delimiter": "comma", 57 | "requireLast": false 58 | }, 59 | } 60 | ], 61 | '@typescript-eslint/no-empty-interface': 'off', 62 | '@typescript-eslint/no-explicit-any': 'off', 63 | '@typescript-eslint/no-inferrable-types': [ 'warn', { 'ignoreParameters': true } ], 64 | '@typescript-eslint/no-non-null-assertion': 'off', 65 | '@typescript-eslint/no-unused-vars': ['error', { 'varsIgnorePattern': '^_', 'argsIgnorePattern': '^_' } ] 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | yarn.lock binary -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # exit when any command fails 4 | set -e 5 | 6 | STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') 7 | [ -z "$STAGED_FILES" ] && exit 0 8 | 9 | # typescript 10 | ./node_modules/.bin/tsc 11 | 12 | # solhint 13 | echo "$STAGED_FILES" | egrep '\.sol$' | xargs ./node_modules/.bin/solhint --fix 14 | 15 | # eslint 16 | echo "$STAGED_FILES" | xargs yarn lint 17 | 18 | # add back the modified/prettified files to staging 19 | echo "$STAGED_FILES" | xargs git add 20 | 21 | exit 0 -------------------------------------------------------------------------------- /.github/workflows/deploy-market.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Market 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | network: 6 | type: choice 7 | description: Network 8 | options: 9 | - fuji 10 | - kovan 11 | - mainnet 12 | - goerli 13 | - mumbai 14 | deployment: 15 | description: Deployment Name (e.g. "usdc") 16 | required: true 17 | simulate: 18 | type: boolean 19 | description: Simulate 20 | eth_pk: 21 | description: Ignore if you plan to use WalletConnect, otherwise, you can paste in a Ethereum private key 22 | jobs: 23 | deploy-market: 24 | name: Deploy Market 25 | runs-on: ubuntu-latest 26 | env: 27 | ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} 28 | SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} 29 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 30 | POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} 31 | steps: 32 | - name: Seacrest 33 | uses: hayesgm/seacrest@v1 34 | with: 35 | ethereum_url: "${{ fromJSON('{\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"kovan\":\"https://kovan.infura.io/v3/$INFURA_KEY\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\"}')[inputs.network] }}" 36 | port: 8585 37 | if: github.event.inputs.eth_pk == '' 38 | 39 | - name: Checkout repository 40 | uses: actions/checkout@v2 41 | 42 | - uses: actions/setup-node@v2 43 | with: 44 | node-version: '16' 45 | 46 | - name: Install packages 47 | run: yarn install --non-interactive --frozen-lockfile 48 | 49 | - name: Compile 50 | run: yarn hardhat compile 51 | 52 | - name: Check types 53 | run: yarn tsc 54 | 55 | - name: Run Deploy 56 | run: | 57 | yarn hardhat deploy --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} 58 | env: 59 | DEBUG: true 60 | ETH_PK: "${{ inputs.eth_pk }}" 61 | NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8585"]')[github.event.inputs.eth_pk == ''] }} 62 | REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} 63 | 64 | - name: Commit changes 65 | if: ${{ github.event.inputs.simulate == 'false' }} 66 | run: | 67 | git config user.name "GitHub Actions Bot" 68 | git config user.email "<>" 69 | git add deployments/\*/roots.json 70 | git commit -m "Modified deployment roots from GitHub Actions" 71 | git push origin 72 | -------------------------------------------------------------------------------- /.github/workflows/prepare-migration.yaml: -------------------------------------------------------------------------------- 1 | name: Prepare Migration 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | network: 6 | type: choice 7 | description: Network 8 | options: 9 | - fuji 10 | - kovan 11 | - mainnet 12 | - goerli 13 | - mumbai 14 | deployment: 15 | description: Deployment Name (e.g. "usdc") 16 | required: true 17 | migration: 18 | description: Migration Name 19 | required: true 20 | simulate: 21 | type: boolean 22 | description: Simulate 23 | eth_pk: 24 | description: Ignore if you plan to use WalletConnect, otherwise, you can paste in a Ethereum private key 25 | jobs: 26 | prepare-migration: 27 | name: Prepare Migration 28 | runs-on: ubuntu-latest 29 | env: 30 | ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} 31 | SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} 32 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 33 | POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} 34 | steps: 35 | - name: Seacrest 36 | uses: hayesgm/seacrest@v1 37 | with: 38 | ethereum_url: "${{ fromJSON('{\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"kovan\":\"https://kovan-eth.compound.finance\",\"mainnet\":\"https://mainnet-eth.compound.finance\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\"}')[inputs.network] }}" 39 | port: 8585 40 | if: github.event.inputs.eth_pk == '' 41 | 42 | - name: Checkout repository 43 | uses: actions/checkout@v2 44 | 45 | - uses: actions/setup-node@v2 46 | with: 47 | node-version: '16' 48 | 49 | - name: Install packages 50 | run: yarn install --non-interactive --frozen-lockfile 51 | 52 | - name: Compile 53 | run: yarn hardhat compile 54 | 55 | - name: Check types 56 | run: yarn tsc 57 | 58 | - name: Run Prepare Migration 59 | run: | 60 | yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --prepare --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ github.event.inputs.migration }} 61 | env: 62 | DEBUG: true 63 | ETH_PK: "${{ inputs.eth_pk }}" 64 | NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8585"]')[github.event.inputs.eth_pk == ''] }} 65 | REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }} 66 | 67 | - uses: actions/upload-artifact@v2 # upload test results 68 | if: success() || failure() # run this step even if previous step failed 69 | with: 70 | name: ${{ github.event.inputs.network }}-${{ github.event.inputs.deployment }}-${{ github.event.inputs.migration }} 71 | path: deployments/${{ github.event.inputs.network }}/${{ github.event.inputs.deployment }}/artifacts/${{ github.event.inputs.migration }}.json 72 | -------------------------------------------------------------------------------- /.github/workflows/run-contract-linter.yaml: -------------------------------------------------------------------------------- 1 | name: Run Contract Linter 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | jobs: 6 | run-contract-linter: 7 | name: Contract linter 8 | runs-on: ubuntu-latest 9 | env: 10 | ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} 11 | SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} 12 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 13 | POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: '16' 22 | 23 | - name: Install packages 24 | run: yarn install --non-interactive --frozen-lockfile && yarn build 25 | 26 | - name: Run linter on modified contracts 27 | run: git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep ^contracts/ | xargs yarn solhint -------------------------------------------------------------------------------- /.github/workflows/run-coverage.yaml: -------------------------------------------------------------------------------- 1 | name: Run Coverage 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | run-coverage: 8 | name: Run coverage 9 | runs-on: ubuntu-latest 10 | env: 11 | NODE_OPTIONS: '--max-old-space-size=4096' 12 | OPTIMIZER_DISABLED: true 13 | ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} 14 | SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} 15 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 16 | POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - uses: actions/setup-node@v2 22 | with: 23 | cache: 'yarn' 24 | node-version: '16' 25 | 26 | - name: Install packages 27 | run: yarn install --non-interactive --frozen-lockfile && yarn build 28 | 29 | - name: Run tests 30 | run: yarn cover 31 | 32 | - name: Upload the coverage reports to Coveralls 33 | uses: coverallsapp/github-action@master 34 | with: 35 | github-token: ${{ secrets.GITHUB_TOKEN }} 36 | -------------------------------------------------------------------------------- /.github/workflows/run-eslint.yaml: -------------------------------------------------------------------------------- 1 | name: Run ESLint 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | jobs: 6 | run-lint: 7 | name: Run ESLint 8 | runs-on: ubuntu-latest 9 | env: 10 | ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} 11 | SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} 12 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 13 | POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v2 17 | 18 | - uses: actions/setup-node@v2 19 | with: 20 | cache: 'yarn' 21 | node-version: '16' 22 | 23 | - name: Install packages 24 | run: yarn install --non-interactive --frozen-lockfile && yarn build 25 | 26 | - name: Run ESLint 27 | run: yarn lint 28 | -------------------------------------------------------------------------------- /.github/workflows/run-gas-profiler.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests With Gas Profiler 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | jobs: 6 | run-gas-profiler: 7 | name: Gas profiler 8 | runs-on: ubuntu-latest 9 | env: 10 | COINMARKETCAP_API_KEY: b52b18a2-d44f-4646-9949-0eb0e9c68574 11 | ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} 12 | SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} 13 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 14 | POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v2 18 | 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: '16' 22 | 23 | - name: Install packages 24 | run: yarn install --non-interactive --frozen-lockfile && yarn build 25 | 26 | - name: Measure 27 | run: yarn gas 28 | -------------------------------------------------------------------------------- /.github/workflows/run-scenarios.yaml: -------------------------------------------------------------------------------- 1 | name: Run Scenarios 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | jobs: 6 | run-scenarios: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | bases: [ development, mainnet, goerli, fuji, mumbai ] 11 | name: Run scenarios 12 | env: 13 | ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} 14 | SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} 15 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 16 | POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | 24 | - uses: actions/setup-node@v2 25 | with: 26 | cache: 'yarn' 27 | node-version: '16' 28 | 29 | - name: Cache Deployments 30 | uses: actions/cache@v2 31 | with: 32 | path: | 33 | deployments/*/.contracts 34 | deployments/**/aliases.json 35 | !deployments/hardhat 36 | !deployments/relations.ts 37 | !deployments/**/roots.json 38 | !deployments/**/relations.ts 39 | !deployments/**/configuration.json 40 | !deployments/**/migrations/* 41 | key: deployments-v4 42 | 43 | - name: Install packages 44 | run: yarn install --non-interactive --frozen-lockfile && yarn build 45 | 46 | - name: Compile 47 | run: npx hardhat compile 48 | 49 | - name: Run scenarios 50 | run: yarn scenario --bases ${{ matrix.bases }} 51 | 52 | - uses: actions/upload-artifact@v2 # upload scenario results 53 | if: success() || failure() # run this step even if previous step failed 54 | with: 55 | name: scenario-results 56 | path: scenario-results.json 57 | 58 | - uses: dorny/test-reporter@v1 59 | with: 60 | name: Scenario Tests (${{ matrix.bases }}) # Name of the check run which will be created 61 | path: scenario-results.json # Path to test results (inside artifact .zip) 62 | reporter: mocha-json # Format of test results 63 | -------------------------------------------------------------------------------- /.github/workflows/run-slither.yaml: -------------------------------------------------------------------------------- 1 | name: Run Slither 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | jobs: 6 | slither-analyzer: 7 | name: Slither analyzer 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout repository 11 | uses: actions/checkout@v2 12 | 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: '16' 16 | 17 | - uses: actions/setup-python@v4 18 | with: 19 | python-version: '3.x' 20 | 21 | - name: Install solc 22 | run: sudo add-apt-repository ppa:ethereum/ethereum;sudo add-apt-repository ppa:ethereum/ethereum-dev;sudo apt-get update;sudo apt-get install solc 23 | 24 | - name: Install packages 25 | run: pip install slither-analyzer solc-select 26 | 27 | - name: Switch to solidity version 28 | run: solc-select install 0.8.15;solc-select use 0.8.15 29 | 30 | - name: Function clash analysis 31 | run: yarn slither:fn-clashes &> tmp_res; cat tmp_res | grep 'Function id collision found' && exit 1 || exit 0 32 | -------------------------------------------------------------------------------- /.github/workflows/run-unit-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Run Unit Tests 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | jobs: 6 | unit-tests: 7 | name: Unit tests 8 | runs-on: ubuntu-latest 9 | env: 10 | ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} 11 | SNOWTRACE_KEY: ${{ secrets.SNOWTRACE_KEY }} 12 | INFURA_KEY: ${{ secrets.INFURA_KEY }} 13 | POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v2 17 | 18 | - uses: actions/setup-node@v2 19 | with: 20 | node-version: '16' 21 | 22 | - name: Install packages 23 | run: yarn install --non-interactive --frozen-lockfile 24 | 25 | - name: Compile 26 | run: yarn hardhat compile 27 | 28 | - name: Check types 29 | run: yarn tsc 30 | 31 | - name: Run tests 32 | run: yarn test 33 | 34 | - uses: actions/upload-artifact@v2 # upload test results 35 | if: success() || failure() # run this step even if previous step failed 36 | with: 37 | name: test-results 38 | path: test-results.json 39 | 40 | - uses: dorny/test-reporter@v1 41 | with: 42 | name: Unit Tests # Name of the check run which will be created 43 | path: 'test-results.json' # Path to test results (inside artifact .zip) 44 | reporter: mocha-json # Format of test results 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore node stuff 2 | node_modules/ 3 | yarn-error.log 4 | 5 | .DS_Store 6 | .vscode 7 | 8 | # Build files 9 | artifacts/ 10 | build/ 11 | cache/ 12 | dist/ 13 | deployments/*/.contracts 14 | deployments/*/*/verify 15 | deployments/*/*/aliases.json 16 | 17 | .env 18 | coverage/ 19 | coverage.json 20 | typechain/ 21 | 22 | test-results.json 23 | scenario-results.json 24 | 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | .env 3 | src/ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.13 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | build 3 | cache 4 | coverage 5 | deployments/*/.contracts 6 | deployments/*/*/*.json 7 | dist 8 | SPEC.md 9 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 100 4 | } 5 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['test/', 'vendor/', 'ERC20.sol'] 3 | }; -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "const-name-snakecase": "off", 6 | "func-visibility": ["warn", { "ignoreConstructors": true }], 7 | "no-empty-blocks": "off", 8 | "not-rely-on-time": "off", 9 | "var-name-mixedcase": "off" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | contracts/test/* 2 | contracts/vendor/* 3 | contracts/liquidator/vendor/* 4 | contracts/liquidator/README.md 5 | contracts/bridges/vendor/* -------------------------------------------------------------------------------- /MIGRATIONS.md: -------------------------------------------------------------------------------- 1 | 2 | # Migrations 3 | 4 | Migrations are simple scripts which deploy or modify contracts. The goal of migration scripts is to make sure that users can see potential changes that are run prior to creating a governance proposal. This is a "nothing up my sleeve" approach to governance preparation (as in, the magician rolls up his sleeves to show there's nothing there-- so the developer deploys scripts from GitHub to show which code was deployed or run). 5 | 6 | ## Creating a Migration 7 | 8 | To create a new migration, run: 9 | 10 | ```sh 11 | yarn hardhat gen:migration --network kovan --deployment usdc my_migration 12 | ``` 13 | 14 | This will create a new file, such as `deployments/kovan/usdc/migrations/164443237_my_migration.ts` with a base migration script. There are currently two steps to a migration script, but this is likely to change soon: 15 | 16 | 1. Prepare: steps used to create artifacts, such as new on-chain contracts. The output from this step is stored (e.g. "NewCometImplementation: 0x...") 17 | 2. Enact: steps used to make these artifacts current, such as upgrading the proxy to the address from the previous step. 18 | 19 | ## Running a Migration Locally 20 | 21 | You can run the preparation for a migration locally via: 22 | 23 | ```sh 24 | yarn hardhat migrate --network kovan --deployment usdc --prepare 164443237_my_migration 25 | ``` 26 | 27 | or the enactment via: 28 | 29 | ```sh 30 | yarn hardhat migrate --network kovan --deployment usdc --enact 164443237_my_migration 31 | ``` 32 | 33 | or both preparation and enactment via: 34 | 35 | ```sh 36 | yarn hardhat migrate --network kovan --deployment usdc --prepare --enact 164443237_my_migration 37 | ``` 38 | 39 | Also, you can simulate either of the previous steps to see what effect they would have without actually modifying the on-chain state: 40 | 41 | ```sh 42 | yarn hardhat migrate --network kovan --deployment usdc --prepare --simulate 164443237_my_migration 43 | ``` 44 | 45 | ## Running a Migration in GitHub 46 | 47 | The preferred way to run a migration is in GitHub, via manual workflow dispatch. The goal of this approach is that it's clear to everyone the exact code that ran, which affords less opportunity for "I'm looking at , but what was deployed was actually ." Look at "Prepare Migration" and "Enact Migration" dispatches in GitHub Actions in this repo (or any fork). 48 | 49 | ## Migration Artifacts 50 | 51 | After preparation, a migration stores some artifacts under `deployments/kovan/usdc/artifacts/164443237_my_migration.json`. These will be loaded and can be referenced in the enact step of that migration. -------------------------------------------------------------------------------- /SCENARIO.md: -------------------------------------------------------------------------------- 1 | # Scenarios 2 | 3 | Scenarios are high-level property and ad-hoc tests for the Comet protocol. To run and check scenarios: 4 | 5 | `npx hardhat scenario` 6 | 7 | ## Running Scenarios 8 | 9 | You can run scenarios against a given base as: 10 | 11 | `npx hardhat scenario --bases development,goerli,fuji` 12 | 13 | You can run spider persistently first if you wish: 14 | 15 | `npx hardhat scenario --spider` 16 | 17 | Note: if you want to speed up, probably better to first: 18 | 19 | `npx hardhat deploy --simulate --overwrite` 20 | 21 | You can change the number of workers: 22 | 23 | `npx hardhat scenario --workers 4` 24 | 25 | ## Adding New Scenarios 26 | 27 | To add a new scenario, add to `scenario/`, e.g. 28 | 29 | **scenario/NewToken.ts** 30 | 31 | ```ts 32 | import { scenario } from './context/CometContext'; 33 | import { expect } from 'chai'; 34 | import { BigNumber } from 'ethers'; 35 | import { World } from '../plugins/scenario'; 36 | 37 | scenario('Comet#allow > allows a user to authorize a manager', { upgrade: true }, async ({ comet, actors }) => { 38 | const { albert, betty } = actors; 39 | 40 | await albert.allow(betty, true); 41 | 42 | expect(await comet.isAllowed(albert.address, betty.address)).to.be.true; 43 | }); 44 | ``` 45 | 46 | For more information, see the Scenarios Hardhat plugin. 47 | 48 | ## Constraints 49 | 50 | ### Modern Constraint 51 | 52 | **requirements**: `{ upgrade: true }` 53 | 54 | This constraint is to indicate that all deployments must use the most recent version of the Comet contract. For instance, say your scenario uses a feature that's not available on certain test-nets (or mainnet), then you would otherwise not be able to run the scenario on those networks. But if you include `{upgrade: true}` in your constraint requirements, the scenario will deploy a new Comet instance and upgrade the proxy to that before running the scenario. Note: currently this simply uses the `deploy.ts` script for deployment. 55 | -------------------------------------------------------------------------------- /SPEC.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/comet/8ac17412ff4a144eca1c35846d416bf4cd28a32f/SPEC.pdf -------------------------------------------------------------------------------- /contracts/CometConfiguration.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @title Compound's Comet Configuration Interface 6 | * @author Compound 7 | */ 8 | contract CometConfiguration { 9 | struct ExtConfiguration { 10 | bytes32 name32; 11 | bytes32 symbol32; 12 | } 13 | 14 | struct Configuration { 15 | address governor; 16 | address pauseGuardian; 17 | address baseToken; 18 | address baseTokenPriceFeed; 19 | address extensionDelegate; 20 | 21 | uint64 supplyKink; 22 | uint64 supplyPerYearInterestRateSlopeLow; 23 | uint64 supplyPerYearInterestRateSlopeHigh; 24 | uint64 supplyPerYearInterestRateBase; 25 | uint64 borrowKink; 26 | uint64 borrowPerYearInterestRateSlopeLow; 27 | uint64 borrowPerYearInterestRateSlopeHigh; 28 | uint64 borrowPerYearInterestRateBase; 29 | uint64 storeFrontPriceFactor; 30 | uint64 trackingIndexScale; 31 | uint64 baseTrackingSupplySpeed; 32 | uint64 baseTrackingBorrowSpeed; 33 | uint104 baseMinForRewards; 34 | uint104 baseBorrowMin; 35 | uint104 targetReserves; 36 | 37 | AssetConfig[] assetConfigs; 38 | } 39 | 40 | struct AssetConfig { 41 | address asset; 42 | address priceFeed; 43 | uint8 decimals; 44 | uint64 borrowCollateralFactor; 45 | uint64 liquidateCollateralFactor; 46 | uint64 liquidationFactor; 47 | uint128 supplyCap; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/CometFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "./Comet.sol"; 5 | import "./CometConfiguration.sol"; 6 | 7 | contract CometFactory is CometConfiguration { 8 | function clone(Configuration calldata config) external returns (address) { 9 | return address(new Comet(config)); 10 | } 11 | } -------------------------------------------------------------------------------- /contracts/CometInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "./CometMainInterface.sol"; 5 | import "./CometExtInterface.sol"; 6 | 7 | /** 8 | * @title Compound's Comet Interface 9 | * @notice An efficient monolithic money market protocol 10 | * @author Compound 11 | */ 12 | abstract contract CometInterface is CometMainInterface, CometExtInterface {} 13 | -------------------------------------------------------------------------------- /contracts/CometMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @title Compound's Comet Math Contract 6 | * @dev Pure math functions 7 | * @author Compound 8 | */ 9 | contract CometMath { 10 | /** Custom errors **/ 11 | 12 | error InvalidUInt64(); 13 | error InvalidUInt104(); 14 | error InvalidUInt128(); 15 | error InvalidInt104(); 16 | error InvalidInt256(); 17 | error NegativeNumber(); 18 | 19 | function safe64(uint n) internal pure returns (uint64) { 20 | if (n > type(uint64).max) revert InvalidUInt64(); 21 | return uint64(n); 22 | } 23 | 24 | function safe104(uint n) internal pure returns (uint104) { 25 | if (n > type(uint104).max) revert InvalidUInt104(); 26 | return uint104(n); 27 | } 28 | 29 | function safe128(uint n) internal pure returns (uint128) { 30 | if (n > type(uint128).max) revert InvalidUInt128(); 31 | return uint128(n); 32 | } 33 | 34 | function signed104(uint104 n) internal pure returns (int104) { 35 | if (n > uint104(type(int104).max)) revert InvalidInt104(); 36 | return int104(n); 37 | } 38 | 39 | function signed256(uint256 n) internal pure returns (int256) { 40 | if (n > uint256(type(int256).max)) revert InvalidInt256(); 41 | return int256(n); 42 | } 43 | 44 | function unsigned104(int104 n) internal pure returns (uint104) { 45 | if (n < 0) revert NegativeNumber(); 46 | return uint104(n); 47 | } 48 | 49 | function unsigned256(int256 n) internal pure returns (uint256) { 50 | if (n < 0) revert NegativeNumber(); 51 | return uint256(n); 52 | } 53 | 54 | function toUInt8(bool x) internal pure returns (uint8) { 55 | return x ? 1 : 0; 56 | } 57 | 58 | function toBool(uint8 x) internal pure returns (bool) { 59 | return x != 0; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/CometProxyAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "./vendor/proxy/transparent/ProxyAdmin.sol"; 5 | 6 | interface Deployable { 7 | function deploy(address cometProxy) external returns (address); 8 | } 9 | 10 | contract CometProxyAdmin is ProxyAdmin { 11 | /** 12 | * @dev Deploy a new Comet and upgrade the implementation of the Comet proxy 13 | * Requirements: 14 | * - This contract must be the admin of `CometProxy` 15 | */ 16 | function deployAndUpgradeTo(Deployable configuratorProxy, TransparentUpgradeableProxy cometProxy) public virtual onlyOwner { 17 | address newCometImpl = configuratorProxy.deploy(address(cometProxy)); 18 | upgrade(cometProxy, newCometImpl); 19 | } 20 | 21 | /** 22 | * @dev Deploy a new Comet and upgrade the implementation of the Comet proxy, then call the function 23 | * Requirements: 24 | * - This contract must be the admin of `CometProxy` 25 | */ 26 | function deployUpgradeToAndCall(Deployable configuratorProxy, TransparentUpgradeableProxy cometProxy, bytes memory data) public virtual onlyOwner { 27 | address newCometImpl = configuratorProxy.deploy(address(cometProxy)); 28 | upgradeAndCall(cometProxy, newCometImpl, data); 29 | } 30 | } -------------------------------------------------------------------------------- /contracts/CometStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @title Compound's Comet Storage Interface 6 | * @dev Versions can enforce append-only storage slots via inheritance. 7 | * @author Compound 8 | */ 9 | contract CometStorage { 10 | // 512 bits total = 2 slots 11 | struct TotalsBasic { 12 | // 1st slot 13 | uint64 baseSupplyIndex; 14 | uint64 baseBorrowIndex; 15 | uint64 trackingSupplyIndex; 16 | uint64 trackingBorrowIndex; 17 | // 2nd slot 18 | uint104 totalSupplyBase; 19 | uint104 totalBorrowBase; 20 | uint40 lastAccrualTime; 21 | uint8 pauseFlags; 22 | } 23 | 24 | struct TotalsCollateral { 25 | uint128 totalSupplyAsset; 26 | uint128 _reserved; 27 | } 28 | 29 | struct UserBasic { 30 | int104 principal; 31 | uint64 baseTrackingIndex; 32 | uint64 baseTrackingAccrued; 33 | uint16 assetsIn; 34 | uint8 _reserved; 35 | } 36 | 37 | struct UserCollateral { 38 | uint128 balance; 39 | uint128 _reserved; 40 | } 41 | 42 | struct LiquidatorPoints { 43 | uint32 numAbsorbs; 44 | uint64 numAbsorbed; 45 | uint128 approxSpend; 46 | uint32 _reserved; 47 | } 48 | 49 | /// @dev Aggregate variables tracked for the entire market 50 | uint64 internal baseSupplyIndex; 51 | uint64 internal baseBorrowIndex; 52 | uint64 internal trackingSupplyIndex; 53 | uint64 internal trackingBorrowIndex; 54 | uint104 internal totalSupplyBase; 55 | uint104 internal totalBorrowBase; 56 | uint40 internal lastAccrualTime; 57 | uint8 internal pauseFlags; 58 | 59 | /// @notice Aggregate variables tracked for each collateral asset 60 | mapping(address => TotalsCollateral) public totalsCollateral; 61 | 62 | /// @notice Mapping of users to accounts which may be permitted to manage the user account 63 | mapping(address => mapping(address => bool)) public isAllowed; 64 | 65 | /// @notice The next expected nonce for an address, for validating authorizations via signature 66 | mapping(address => uint) public userNonce; 67 | 68 | /// @notice Mapping of users to base principal and other basic data 69 | mapping(address => UserBasic) public userBasic; 70 | 71 | /// @notice Mapping of users to collateral data per collateral asset 72 | mapping(address => mapping(address => UserCollateral)) public userCollateral; 73 | 74 | /// @notice Mapping of magic liquidator points 75 | mapping(address => LiquidatorPoints) public liquidatorPoints; 76 | } 77 | -------------------------------------------------------------------------------- /contracts/ConfiguratorProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "./vendor/proxy/transparent/TransparentUpgradeableProxy.sol"; 5 | 6 | /** 7 | * @dev A TransparentUpgradeableProxy that allows its admin to call its implementation. 8 | */ 9 | contract ConfiguratorProxy is TransparentUpgradeableProxy { 10 | /** 11 | * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and 12 | * optionally initialized with `_data` as explained in {UpgradeableProxy-constructor}. 13 | */ 14 | constructor(address _logic, address _admin, bytes memory _data) payable TransparentUpgradeableProxy(_logic, _admin, _data) {} 15 | 16 | /** 17 | * @dev Overrides the TransparentUpgradeableProxy's _beforeFallback so admin can call the implementation. 18 | */ 19 | function _beforeFallback() internal virtual override {} 20 | } 21 | -------------------------------------------------------------------------------- /contracts/ConfiguratorStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "./CometConfiguration.sol"; 5 | 6 | /** 7 | * @title Compound's Comet Configuration Storage Interface 8 | * @dev Versions can enforce append-only storage slots via inheritance. 9 | * @author Compound 10 | */ 11 | contract ConfiguratorStorage is CometConfiguration { 12 | /// @notice The current version of Configurator. This version should be 13 | /// checked in the initializer function. 14 | uint public version; 15 | 16 | /// @notice Mapping of Comet proxy addresses to their Configuration settings 17 | /// @dev This needs to be internal to avoid a `CompilerError: Stack too deep 18 | /// when compiling inline assembly` error that is caused by the default 19 | /// getters created for public variables. 20 | mapping(address => Configuration) internal configuratorParams; 21 | 22 | /// @notice The governor of the protocol 23 | address public governor; 24 | 25 | /// @notice Mapping of Comet proxy addresses to their Comet factory contracts 26 | mapping(address => address) public factory; 27 | } -------------------------------------------------------------------------------- /contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @title ERC 20 Token Standard Interface 6 | * https://eips.ethereum.org/EIPS/eip-20 7 | */ 8 | interface ERC20 { 9 | function name() external view returns (string memory); 10 | function symbol() external view returns (string memory); 11 | function decimals() external view returns (uint8); 12 | 13 | /** 14 | * @notice Get the total number of tokens in circulation 15 | * @return The supply of tokens 16 | */ 17 | function totalSupply() external view returns (uint256); 18 | 19 | /** 20 | * @notice Gets the balance of the specified address 21 | * @param owner The address from which the balance will be retrieved 22 | * @return The balance 23 | */ 24 | function balanceOf(address owner) external view returns (uint256); 25 | 26 | /** 27 | * @notice Transfer `amount` tokens from `msg.sender` to `dst` 28 | * @param dst The address of the destination account 29 | * @param amount The number of tokens to transfer 30 | * @return Whether or not the transfer succeeded 31 | */ 32 | function transfer(address dst, uint256 amount) external returns (bool); 33 | 34 | /** 35 | * @notice Transfer `amount` tokens from `src` to `dst` 36 | * @param src The address of the source account 37 | * @param dst The address of the destination account 38 | * @param amount The number of tokens to transfer 39 | * @return Whether or not the transfer succeeded 40 | */ 41 | function transferFrom(address src, address dst, uint256 amount) external returns (bool); 42 | 43 | /** 44 | * @notice Approve `spender` to transfer up to `amount` from `src` 45 | * @dev This will overwrite the approval amount for `spender` 46 | * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) 47 | * @param spender The address of the account which may transfer tokens 48 | * @param amount The number of tokens that are approved (-1 means infinite) 49 | * @return Whether or not the approval succeeded 50 | */ 51 | function approve(address spender, uint256 amount) external returns (bool); 52 | 53 | /** 54 | * @notice Get the current allowance from `owner` for `spender` 55 | * @param owner The address of the account which owns the tokens to be spent 56 | * @param spender The address of the account which may transfer tokens 57 | * @return The number of tokens allowed to be spent (-1 means infinite) 58 | */ 59 | function allowance(address owner, address spender) external view returns (uint256); 60 | 61 | event Transfer(address indexed from, address indexed to, uint256 amount); 62 | event Approval(address indexed owner, address indexed spender, uint256 amount); 63 | } 64 | -------------------------------------------------------------------------------- /contracts/IComp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "./ERC20.sol"; 5 | 6 | /** 7 | * @dev Interface for interacting with COMP. 8 | * Note Not a comprehensive interface 9 | */ 10 | interface IComp is ERC20 { 11 | function delegate(address delegatee) external; 12 | function getCurrentVotes(address account) external view returns (uint96); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/IGovernorBravo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @dev Interface for interacting with Governor bravo. 6 | * Note Not a comprehensive interface 7 | */ 8 | interface IGovernorBravo { 9 | enum ProposalState { 10 | Pending, 11 | Active, 12 | Canceled, 13 | Defeated, 14 | Succeeded, 15 | Queued, 16 | Expired, 17 | Executed 18 | } 19 | 20 | struct Proposal { 21 | uint id; 22 | address proposer; 23 | uint eta; 24 | uint startBlock; 25 | uint endBlock; 26 | uint forVotes; 27 | uint againstVotes; 28 | uint abstainVotes; 29 | bool canceled; 30 | bool executed; 31 | } 32 | 33 | event ProposalCreated( 34 | uint256 proposalId, 35 | address proposer, 36 | address[] targets, 37 | uint256[] values, 38 | string[] signatures, 39 | bytes[] calldatas, 40 | uint256 startBlock, 41 | uint256 endBlock, 42 | string description 43 | ); 44 | event ProposalCanceled(uint256 proposalId); 45 | event ProposalQueued(uint256 proposalId, uint256 eta); 46 | event ProposalExecuted(uint256 proposalId); 47 | 48 | function MIN_VOTING_PERIOD() external view returns (uint256); 49 | function MIN_VOTING_DELAY() external view returns (uint256); 50 | function MIN_PROPOSAL_THRESHOLD() external view returns (uint256); 51 | 52 | function comp() external view returns (address); 53 | function proposalCount() external view returns (uint256); 54 | function proposals(uint256 proposalId) external view returns (Proposal memory); 55 | function votingDelay() external view returns (uint256); 56 | function votingPeriod() external view returns (uint256); 57 | function state(uint256 proposalId) external view returns (ProposalState); 58 | function propose( 59 | address[] memory targets, 60 | uint256[] memory values, 61 | string[] memory signatures, 62 | bytes[] memory calldatas, 63 | string memory description 64 | ) external returns (uint256 proposalId); 65 | function queue(uint256 proposalId) external payable; 66 | function execute(uint256 proposalId) external payable; 67 | function castVote(uint256 proposalId, uint8 support) external returns (uint256 balance); 68 | } -------------------------------------------------------------------------------- /contracts/IProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @dev Interface for interacting with a basic proxy. 6 | * Note Not a comprehensive interface 7 | */ 8 | interface IProxy { 9 | function implementation() external view returns (address); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/ITimelock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @dev Interface for interacting with a Timelock 6 | */ 7 | interface ITimelock { 8 | event NewAdmin(address indexed newAdmin); 9 | event NewPendingAdmin(address indexed newPendingAdmin); 10 | event NewDelay(uint indexed newDelay); 11 | event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 12 | event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 13 | event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 14 | 15 | function GRACE_PERIOD() virtual external view returns (uint); 16 | function MINIMUM_DELAY() virtual external view returns (uint); 17 | function MAXIMUM_DELAY() virtual external view returns (uint); 18 | 19 | function admin() virtual external view returns (address); 20 | function pendingAdmin() virtual external view returns (address); 21 | function setPendingAdmin(address pendingAdmin_) virtual external; 22 | function acceptAdmin() virtual external; 23 | 24 | function delay() virtual external view returns (uint); 25 | function setDelay(uint delay) virtual external; 26 | 27 | function queuedTransactions(bytes32 txHash) virtual external returns (bool); 28 | function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) virtual external returns (bytes32); 29 | function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) virtual external; 30 | function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) virtual external payable returns (bytes memory); 31 | } 32 | -------------------------------------------------------------------------------- /contracts/IWETH9.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | interface IWETH9 { 5 | function name() external view returns (string memory); 6 | 7 | function symbol() external view returns (string memory); 8 | 9 | function decimals() external view returns (uint8); 10 | 11 | function balanceOf(address) external view returns (uint); 12 | 13 | function allowance(address, address) external view returns (uint); 14 | 15 | receive() external payable; 16 | 17 | function deposit() external payable; 18 | 19 | function withdraw(uint wad) external; 20 | 21 | function totalSupply() external view returns (uint); 22 | 23 | function approve(address guy, uint wad) external returns (bool); 24 | 25 | function transfer(address dst, uint wad) external returns (bool); 26 | 27 | function transferFrom(address src, address dst, uint wad) 28 | external 29 | returns (bool); 30 | } -------------------------------------------------------------------------------- /contracts/bridges/polygon/PolygonBridgeReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "../vendor/fx-portal/contracts/FxChild.sol"; 5 | import "../BaseBridgeReceiver.sol"; 6 | 7 | contract PolygonBridgeReceiver is IFxMessageProcessor, BaseBridgeReceiver { 8 | error InvalidChild(); 9 | 10 | event NewFxChild(address indexed oldFxChild, address indexed newFxChild); 11 | 12 | address public fxChild; 13 | 14 | constructor(address _fxChild) { 15 | fxChild = _fxChild; 16 | } 17 | 18 | function changeFxChild(address newFxChild) public { 19 | if (msg.sender != localTimelock) revert Unauthorized(); 20 | address oldFxChild = fxChild; 21 | fxChild = newFxChild; 22 | emit NewFxChild(oldFxChild, newFxChild); 23 | } 24 | 25 | function processMessageFromRoot( 26 | uint256 stateId, 27 | address messageSender, 28 | bytes calldata data 29 | ) public override { 30 | if (msg.sender != fxChild) revert InvalidChild(); 31 | processMessage(messageSender, data); 32 | } 33 | } -------------------------------------------------------------------------------- /contracts/bridges/test/BaseBridgeReceiverHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "../BaseBridgeReceiver.sol"; 5 | 6 | contract BaseBridgeReceiverHarness is BaseBridgeReceiver { 7 | function processMessageExternal( 8 | address messageSender, 9 | bytes calldata data 10 | ) external { 11 | processMessage(messageSender, data); 12 | } 13 | } -------------------------------------------------------------------------------- /contracts/bridges/vendor/fx-portal/contracts/FxChild.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | // IFxMessageProcessor represents interface to process message 5 | interface IFxMessageProcessor { 6 | function processMessageFromRoot( 7 | uint256 stateId, 8 | address rootMessageSender, 9 | bytes calldata data 10 | ) external; 11 | } -------------------------------------------------------------------------------- /contracts/bridges/vendor/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "strict": true, 3 | "files": { 4 | "fx-portal/contracts/FxChild.sol": { 5 | "source": { 6 | "git": { 7 | "repo": "git@github.com:fx-portal/contracts.git", 8 | "commit": "ebd046507d76cd03fa2b2559257091471a259ed7", 9 | "path": "contracts/FxChild.sol" 10 | } 11 | }, 12 | "patches": [ 13 | { 14 | "oldStart": 1, 15 | "oldLines": 11, 16 | "newStart": 1, 17 | "newLines": 37, 18 | "lines": [ 19 | " // SPDX-License-Identifier: MIT", 20 | " pragma solidity ^0.8.0;", 21 | " ", 22 | "+// IStateReceiver represents interface to receive state", 23 | "+interface IStateReceiver {", 24 | "+ function onStateReceive(uint256 stateId, bytes calldata data) external;", 25 | "+}", 26 | "+", 27 | " // IFxMessageProcessor represents interface to process message", 28 | " interface IFxMessageProcessor {", 29 | " function processMessageFromRoot(", 30 | "\\ No newline at end of file", 31 | " uint256 stateId,", 32 | " address rootMessageSender,", 33 | " bytes calldata data", 34 | " ) external;", 35 | "-}", 36 | "+}", 37 | "+", 38 | "+/**", 39 | "+ * @title FxChild child contract for state receiver", 40 | "+ */", 41 | "+contract FxChild is IStateReceiver {", 42 | "+ address public fxRoot;", 43 | "+", 44 | "+ event NewFxMessage(address rootMessageSender, address receiver, bytes data);", 45 | "+", 46 | "+ function setFxRoot(address _fxRoot) external {", 47 | "+ require(fxRoot == address(0x0));", 48 | "+ fxRoot = _fxRoot;", 49 | "+ }", 50 | "+", 51 | "+ function onStateReceive(uint256 stateId, bytes calldata _data) external override {", 52 | "+ require(msg.sender == address(0x0000000000000000000000000000000000001001), \"Invalid sender\");", 53 | "+ (address rootMessageSender, address receiver, bytes memory data) = abi.decode(_data, (address, address, bytes));", 54 | "+ emit NewFxMessage(rootMessageSender, receiver, data);", 55 | "+ IFxMessageProcessor(receiver).processMessageFromRoot(stateId, rootMessageSender, data);", 56 | "+ }", 57 | "+}" 58 | ] 59 | } 60 | ] 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /contracts/liquidator/README.md: -------------------------------------------------------------------------------- 1 | # Liquidation bot 2 | 3 | ## Introduction 4 | 5 | This liquidaton bot is a starting point for the community members interested in arbitrage opportunities with Compound III. It uses Uniswap flash loans to purchase discounted collateral assets from underwater accounts. 6 | 7 | ## Liquidator logic 8 | 9 | The liquidation bot executes the following actions: 10 | 1. Absorbs collateral positions. 11 | 2. Borrows base token from given Uniswap pool using flashswap functionality. 12 | 3. Buys discounted collateral from protocol. 13 | 4. Exchanges collateral assets into base token using other Uniswap pools. 14 | 5. Pays back flash loan. 15 | 6. Sends profit in base token to the caller of Liquidator contract. 16 | 17 | 18 | For documentation of Uniswap Flash Swaps, see [uniswap/flash-swaps](https://docs.uniswap.org/protocol/guides/flash-integrations/inheritance-constructors). 19 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@openzeppelin/contracts/token/ERC20/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC20 standard as defined in the EIP. 8 | */ 9 | interface IERC20 { 10 | /** 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 12 | * another (`to`). 13 | * 14 | * Note that `value` may be zero. 15 | */ 16 | event Transfer(address indexed from, address indexed to, uint256 value); 17 | 18 | /** 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 20 | * a call to {approve}. `value` is the new allowance. 21 | */ 22 | event Approval(address indexed owner, address indexed spender, uint256 value); 23 | 24 | /** 25 | * @dev Returns the amount of tokens in existence. 26 | */ 27 | function totalSupply() external view returns (uint256); 28 | 29 | /** 30 | * @dev Returns the amount of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves `amount` tokens from the caller's account to `to`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address to, uint256 amount) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 54 | * 55 | * Returns a boolean value indicating whether the operation succeeded. 56 | * 57 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 58 | * that someone may use both the old and the new allowance by unfortunate 59 | * transaction ordering. One possible solution to mitigate this race 60 | * condition is to first reduce the spender's allowance to 0 and set the 61 | * desired value afterwards: 62 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 63 | * 64 | * Emits an {Approval} event. 65 | */ 66 | function approve(address spender, uint256 amount) external returns (bool); 67 | 68 | /** 69 | * @dev Moves `amount` tokens from `from` to `to` using the 70 | * allowance mechanism. `amount` is then deducted from the caller's 71 | * allowance. 72 | * 73 | * Returns a boolean value indicating whether the operation succeeded. 74 | * 75 | * Emits a {Transfer} event. 76 | */ 77 | function transferFrom( 78 | address from, 79 | address to, 80 | uint256 amount 81 | ) external returns (bool); 82 | } 83 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | import './pool/IUniswapV3PoolImmutables.sol'; 5 | import './pool/IUniswapV3PoolState.sol'; 6 | import './pool/IUniswapV3PoolDerivedState.sol'; 7 | import './pool/IUniswapV3PoolActions.sol'; 8 | import './pool/IUniswapV3PoolOwnerActions.sol'; 9 | import './pool/IUniswapV3PoolEvents.sol'; 10 | 11 | /// @title The interface for a Uniswap V3 Pool 12 | /// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform 13 | /// to the ERC20 specification 14 | /// @dev The pool interface is broken up into many smaller pieces 15 | interface IUniswapV3Pool is 16 | IUniswapV3PoolImmutables, 17 | IUniswapV3PoolState, 18 | IUniswapV3PoolDerivedState, 19 | IUniswapV3PoolActions, 20 | IUniswapV3PoolOwnerActions, 21 | IUniswapV3PoolEvents 22 | { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Callback for IUniswapV3PoolActions#flash 5 | /// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface 6 | interface IUniswapV3FlashCallback { 7 | /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash. 8 | /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. 9 | /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. 10 | /// @param fee0 The fee amount in token0 due to the pool by the end of the flash 11 | /// @param fee1 The fee amount in token1 due to the pool by the end of the flash 12 | /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call 13 | function uniswapV3FlashCallback( 14 | uint256 fee0, 15 | uint256 fee1, 16 | bytes calldata data 17 | ) external; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Callback for IUniswapV3PoolActions#swap 5 | /// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface 6 | interface IUniswapV3SwapCallback { 7 | /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. 8 | /// @dev In the implementation you must pay the pool tokens owed for the swap. 9 | /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. 10 | /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. 11 | /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by 12 | /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. 13 | /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by 14 | /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. 15 | /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call 16 | function uniswapV3SwapCallback( 17 | int256 amount0Delta, 18 | int256 amount1Delta, 19 | bytes calldata data 20 | ) external; 21 | } 22 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Pool state that is not stored 5 | /// @notice Contains view functions to provide information about the pool that is computed rather than stored on the 6 | /// blockchain. The functions here may have variable gas costs. 7 | interface IUniswapV3PoolDerivedState { 8 | /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp 9 | /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing 10 | /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, 11 | /// you must call it with secondsAgos = [3600, 0]. 12 | /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in 13 | /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. 14 | /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned 15 | /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp 16 | /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block 17 | /// timestamp 18 | function observe(uint32[] calldata secondsAgos) 19 | external 20 | view 21 | returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); 22 | 23 | /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range 24 | /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. 25 | /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first 26 | /// snapshot is taken and the second snapshot is taken. 27 | /// @param tickLower The lower tick of the range 28 | /// @param tickUpper The upper tick of the range 29 | /// @return tickCumulativeInside The snapshot of the tick accumulator for the range 30 | /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range 31 | /// @return secondsInside The snapshot of seconds per liquidity for the range 32 | function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) 33 | external 34 | view 35 | returns ( 36 | int56 tickCumulativeInside, 37 | uint160 secondsPerLiquidityInsideX128, 38 | uint32 secondsInside 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolImmutables.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Pool state that never changes 5 | /// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values 6 | interface IUniswapV3PoolImmutables { 7 | /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface 8 | /// @return The contract address 9 | function factory() external view returns (address); 10 | 11 | /// @notice The first of the two tokens of the pool, sorted by address 12 | /// @return The token contract address 13 | function token0() external view returns (address); 14 | 15 | /// @notice The second of the two tokens of the pool, sorted by address 16 | /// @return The token contract address 17 | function token1() external view returns (address); 18 | 19 | /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 20 | /// @return The fee 21 | function fee() external view returns (uint24); 22 | 23 | /// @notice The pool tick spacing 24 | /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive 25 | /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... 26 | /// This value is an int24 to avoid casting even though it is always positive. 27 | /// @return The tick spacing 28 | function tickSpacing() external view returns (int24); 29 | 30 | /// @notice The maximum amount of position liquidity that can use any tick in the range 31 | /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and 32 | /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool 33 | /// @return The max amount of liquidity per tick 34 | function maxLiquidityPerTick() external view returns (uint128); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Permissioned pool actions 5 | /// @notice Contains pool methods that may only be called by the factory owner 6 | interface IUniswapV3PoolOwnerActions { 7 | /// @notice Set the denominator of the protocol's % share of the fees 8 | /// @param feeProtocol0 new protocol fee for token0 of the pool 9 | /// @param feeProtocol1 new protocol fee for token1 of the pool 10 | function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; 11 | 12 | /// @notice Collect the protocol fee accrued to the pool 13 | /// @param recipient The address to which collected protocol fees should be sent 14 | /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 15 | /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 16 | /// @return amount0 The protocol fee collected in token0 17 | /// @return amount1 The protocol fee collected in token1 18 | function collectProtocol( 19 | address recipient, 20 | uint128 amount0Requested, 21 | uint128 amount1Requested 22 | ) external returns (uint128 amount0, uint128 amount1); 23 | } 24 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/base/PeripheryImmutableState.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.15; 3 | 4 | import '../interfaces/IPeripheryImmutableState.sol'; 5 | 6 | /// @title Immutable state 7 | /// @notice Immutable state used by periphery contracts 8 | abstract contract PeripheryImmutableState is IPeripheryImmutableState { 9 | /// @inheritdoc IPeripheryImmutableState 10 | address public immutable override factory; 11 | /// @inheritdoc IPeripheryImmutableState 12 | address public immutable override WETH9; 13 | 14 | constructor(address _factory, address _WETH9) { 15 | factory = _factory; 16 | WETH9 = _WETH9; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/base/PeripheryPayments.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.7.5; 3 | 4 | import '../../../../@openzeppelin/contracts/token/ERC20/IERC20.sol'; 5 | 6 | import '../interfaces/IPeripheryPayments.sol'; 7 | import '../interfaces/external/IWETH9.sol'; 8 | 9 | import '../libraries/TransferHelper.sol'; 10 | 11 | import './PeripheryImmutableState.sol'; 12 | 13 | abstract contract PeripheryPayments is IPeripheryPayments, PeripheryImmutableState { 14 | receive() external payable { 15 | require(msg.sender == WETH9, 'Not WETH9'); 16 | } 17 | 18 | /// @inheritdoc IPeripheryPayments 19 | function unwrapWETH9(uint256 amountMinimum, address recipient) public payable override { 20 | uint256 balanceWETH9 = IWETH9(WETH9).balanceOf(address(this)); 21 | require(balanceWETH9 >= amountMinimum, 'Insufficient WETH9'); 22 | 23 | if (balanceWETH9 > 0) { 24 | IWETH9(WETH9).withdraw(balanceWETH9); 25 | TransferHelper.safeTransferETH(recipient, balanceWETH9); 26 | } 27 | } 28 | 29 | /// @inheritdoc IPeripheryPayments 30 | function sweepToken( 31 | address token, 32 | uint256 amountMinimum, 33 | address recipient 34 | ) public payable override { 35 | uint256 balanceToken = IERC20(token).balanceOf(address(this)); 36 | require(balanceToken >= amountMinimum, 'Insufficient token'); 37 | 38 | if (balanceToken > 0) { 39 | TransferHelper.safeTransfer(token, recipient, balanceToken); 40 | } 41 | } 42 | 43 | /// @inheritdoc IPeripheryPayments 44 | function refundETH() external payable override { 45 | if (address(this).balance > 0) TransferHelper.safeTransferETH(msg.sender, address(this).balance); 46 | } 47 | 48 | /// @param token The token to pay 49 | /// @param payer The entity that must pay 50 | /// @param recipient The entity that will receive payment 51 | /// @param value The amount to pay 52 | function pay( 53 | address token, 54 | address payer, 55 | address recipient, 56 | uint256 value 57 | ) internal { 58 | if (token == WETH9 && address(this).balance >= value) { 59 | // pay with WETH9 60 | IWETH9(WETH9).deposit{value: value}(); // wrap only what is needed to pay 61 | IWETH9(WETH9).transfer(recipient, value); 62 | } else if (payer == address(this)) { 63 | // pay with tokens already in the contract (for the exact input multihop case) 64 | TransferHelper.safeTransfer(token, recipient, value); 65 | } else { 66 | // pull payment 67 | TransferHelper.safeTransferFrom(token, payer, recipient, value); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/interfaces/IPeripheryImmutableState.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Immutable state 5 | /// @notice Functions that return immutable state of the router 6 | interface IPeripheryImmutableState { 7 | /// @return Returns the address of the Uniswap V3 factory 8 | function factory() external view returns (address); 9 | 10 | /// @return Returns the address of WETH9 11 | function WETH9() external view returns (address); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/interfaces/IPeripheryPayments.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.7.5; 3 | 4 | /// @title Periphery Payments 5 | /// @notice Functions to ease deposits and withdrawals of ETH 6 | interface IPeripheryPayments { 7 | /// @notice Unwraps the contract's WETH9 balance and sends it to recipient as ETH. 8 | /// @dev The amountMinimum parameter prevents malicious contracts from stealing WETH9 from users. 9 | /// @param amountMinimum The minimum amount of WETH9 to unwrap 10 | /// @param recipient The address receiving ETH 11 | function unwrapWETH9(uint256 amountMinimum, address recipient) external payable; 12 | 13 | /// @notice Refunds any ETH balance held by this contract to the `msg.sender` 14 | /// @dev Useful for bundling with mint or increase liquidity that uses ether, or exact output swaps 15 | /// that use ether for the input amount 16 | function refundETH() external payable; 17 | 18 | /// @notice Transfers the full amount of a token held by this contract to recipient 19 | /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users 20 | /// @param token The contract address of the token which will be transferred to `recipient` 21 | /// @param amountMinimum The minimum amount of token required for a transfer 22 | /// @param recipient The destination address of the token 23 | function sweepToken( 24 | address token, 25 | uint256 amountMinimum, 26 | address recipient 27 | ) external payable; 28 | } 29 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.7.5; 3 | pragma abicoder v2; 4 | 5 | import '../../../v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol'; 6 | 7 | /// @title Router token swapping functionality 8 | /// @notice Functions for swapping tokens via Uniswap V3 9 | interface ISwapRouter is IUniswapV3SwapCallback { 10 | struct ExactInputSingleParams { 11 | address tokenIn; 12 | address tokenOut; 13 | uint24 fee; 14 | address recipient; 15 | uint256 deadline; 16 | uint256 amountIn; 17 | uint256 amountOutMinimum; 18 | uint160 sqrtPriceLimitX96; 19 | } 20 | 21 | /// @notice Swaps `amountIn` of one token for as much as possible of another token 22 | /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata 23 | /// @return amountOut The amount of the received token 24 | function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); 25 | 26 | struct ExactInputParams { 27 | bytes path; 28 | address recipient; 29 | uint256 deadline; 30 | uint256 amountIn; 31 | uint256 amountOutMinimum; 32 | } 33 | 34 | /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path 35 | /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata 36 | /// @return amountOut The amount of the received token 37 | function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); 38 | 39 | struct ExactOutputSingleParams { 40 | address tokenIn; 41 | address tokenOut; 42 | uint24 fee; 43 | address recipient; 44 | uint256 deadline; 45 | uint256 amountOut; 46 | uint256 amountInMaximum; 47 | uint160 sqrtPriceLimitX96; 48 | } 49 | 50 | /// @notice Swaps as little as possible of one token for `amountOut` of another token 51 | /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata 52 | /// @return amountIn The amount of the input token 53 | function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); 54 | 55 | struct ExactOutputParams { 56 | bytes path; 57 | address recipient; 58 | uint256 deadline; 59 | uint256 amountOut; 60 | uint256 amountInMaximum; 61 | } 62 | 63 | /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) 64 | /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata 65 | /// @return amountIn The amount of the input token 66 | function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); 67 | } 68 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/interfaces/external/IWETH9.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.15; 3 | 4 | import '../../../../../@openzeppelin/contracts/token/ERC20/IERC20.sol'; 5 | 6 | /// @title Interface for WETH9 7 | interface IWETH9 is IERC20 { 8 | /// @notice Deposit ether to get wrapped ether 9 | function deposit() external payable; 10 | 11 | /// @notice Withdraw wrapped ether to get ether 12 | function withdraw(uint256) external; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/libraries/CallbackValidation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.15; 3 | 4 | import '../../../v3-core/contracts/interfaces/IUniswapV3Pool.sol'; 5 | import './PoolAddress.sol'; 6 | 7 | /// @notice Provides validation for callbacks from Uniswap V3 Pools 8 | library CallbackValidation { 9 | /// @notice Returns the address of a valid Uniswap V3 Pool 10 | /// @param factory The contract address of the Uniswap V3 factory 11 | /// @param tokenA The contract address of either token0 or token1 12 | /// @param tokenB The contract address of the other token 13 | /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip 14 | /// @return pool The V3 pool contract address 15 | function verifyCallback( 16 | address factory, 17 | address tokenA, 18 | address tokenB, 19 | uint24 fee 20 | ) internal view returns (IUniswapV3Pool pool) { 21 | return verifyCallback(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee)); 22 | } 23 | 24 | /// @notice Returns the address of a valid Uniswap V3 Pool 25 | /// @param factory The contract address of the Uniswap V3 factory 26 | /// @param poolKey The identifying key of the V3 pool 27 | /// @return pool The V3 pool contract address 28 | function verifyCallback(address factory, PoolAddress.PoolKey memory poolKey) 29 | internal 30 | view 31 | returns (IUniswapV3Pool pool) 32 | { 33 | pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); 34 | require(msg.sender == address(pool)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/libraries/PoolAddress.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Provides functions for deriving a pool address from the factory, tokens, and the fee 5 | library PoolAddress { 6 | bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; 7 | 8 | /// @notice The identifying key of the pool 9 | struct PoolKey { 10 | address token0; 11 | address token1; 12 | uint24 fee; 13 | } 14 | 15 | /// @notice Returns PoolKey: the ordered tokens with the matched fee levels 16 | /// @param tokenA The first token of a pool, unsorted 17 | /// @param tokenB The second token of a pool, unsorted 18 | /// @param fee The fee level of the pool 19 | /// @return Poolkey The pool details with ordered token0 and token1 assignments 20 | function getPoolKey( 21 | address tokenA, 22 | address tokenB, 23 | uint24 fee 24 | ) internal pure returns (PoolKey memory) { 25 | if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); 26 | return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); 27 | } 28 | 29 | /// @notice Deterministically computes the pool address given the factory and PoolKey 30 | /// @param factory The Uniswap V3 factory contract address 31 | /// @param key The PoolKey 32 | /// @return pool The contract address of the V3 pool 33 | function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) { 34 | require(key.token0 < key.token1); 35 | pool = address( 36 | uint160( 37 | uint256( 38 | keccak256( 39 | abi.encodePacked( 40 | hex'ff', 41 | factory, 42 | keccak256(abi.encode(key.token0, key.token1, key.fee)), 43 | POOL_INIT_CODE_HASH 44 | ) 45 | ) 46 | ) 47 | ) 48 | ); 49 | } 50 | } -------------------------------------------------------------------------------- /contracts/liquidator/vendor/@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.6.0; 3 | 4 | import '../../../../@openzeppelin/contracts/token/ERC20/IERC20.sol'; 5 | 6 | library TransferHelper { 7 | /// @notice Transfers tokens from the targeted address to the given destination 8 | /// @notice Errors with 'STF' if transfer fails 9 | /// @param token The contract address of the token to be transferred 10 | /// @param from The originating address from which the tokens will be transferred 11 | /// @param to The destination address of the transfer 12 | /// @param value The amount to be transferred 13 | function safeTransferFrom( 14 | address token, 15 | address from, 16 | address to, 17 | uint256 value 18 | ) internal { 19 | (bool success, bytes memory data) = 20 | token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value)); 21 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF'); 22 | } 23 | 24 | /// @notice Transfers tokens from msg.sender to a recipient 25 | /// @dev Errors with ST if transfer fails 26 | /// @param token The contract address of the token which will be transferred 27 | /// @param to The recipient of the transfer 28 | /// @param value The value of the transfer 29 | function safeTransfer( 30 | address token, 31 | address to, 32 | uint256 value 33 | ) internal { 34 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value)); 35 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST'); 36 | } 37 | 38 | /// @notice Approves the stipulated contract to spend the given allowance in the given token 39 | /// @dev Errors with 'SA' if transfer fails 40 | /// @param token The contract address of the token to be approved 41 | /// @param to The target of the approval 42 | /// @param value The amount of the given token the target will be allowed to spend 43 | function safeApprove( 44 | address token, 45 | address to, 46 | uint256 value 47 | ) internal { 48 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value)); 49 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA'); 50 | } 51 | 52 | /// @notice Transfers ETH to the recipient address 53 | /// @dev Fails with `STE` 54 | /// @param to The destination of the transfer 55 | /// @param value The value to be transferred 56 | function safeTransferETH(address to, uint256 value) internal { 57 | (bool success, ) = to.call{value: value}(new bytes(0)); 58 | require(success, 'STE'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/test/CometHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "../Comet.sol"; 5 | 6 | contract CometHarness is Comet { 7 | uint public nowOverride; 8 | 9 | constructor(Configuration memory config) Comet(config) {} 10 | 11 | function getNowInternal() override internal view returns (uint40) { 12 | return nowOverride > 0 ? uint40(nowOverride) : super.getNowInternal(); 13 | } 14 | 15 | function getNow() public view returns (uint40) { 16 | return getNowInternal(); 17 | } 18 | 19 | function setNow(uint now_) external { 20 | nowOverride = now_; 21 | } 22 | 23 | function setTotalsBasic(TotalsBasic memory totals) external { 24 | baseSupplyIndex = totals.baseSupplyIndex; 25 | baseBorrowIndex = totals.baseBorrowIndex; 26 | trackingSupplyIndex = totals.trackingSupplyIndex; 27 | trackingBorrowIndex = totals.trackingBorrowIndex; 28 | totalSupplyBase = totals.totalSupplyBase; 29 | totalBorrowBase = totals.totalBorrowBase; 30 | lastAccrualTime = totals.lastAccrualTime; 31 | } 32 | 33 | function setTotalsCollateral(address asset, TotalsCollateral memory totals) external { 34 | totalsCollateral[asset] = totals; 35 | } 36 | 37 | function setBasePrincipal(address account, int104 principal) external { 38 | userBasic[account].principal = principal; 39 | } 40 | 41 | function setCollateralBalance(address account, address asset, uint128 balance) external { 42 | uint128 oldBalance = userCollateral[account][asset].balance; 43 | userCollateral[account][asset].balance = balance; 44 | AssetInfo memory assetInfo = getAssetInfoByAddress(asset); 45 | updateAssetsIn(account, assetInfo, oldBalance, balance); 46 | } 47 | 48 | function updateAssetsInExternal( 49 | address account, 50 | address asset, 51 | uint128 initialUserBalance, 52 | uint128 finalUserBalance 53 | ) external { 54 | AssetInfo memory assetInfo = getAssetInfoByAddress(asset); 55 | updateAssetsIn(account, assetInfo, initialUserBalance, finalUserBalance); 56 | } 57 | 58 | function getAssetList(address account) external view returns (address[] memory result) { 59 | uint16 assetsIn = userBasic[account].assetsIn; 60 | 61 | uint8 count = 0; 62 | for (uint8 i = 0; i < numAssets; i++) { 63 | if (isInAsset(assetsIn, i)) { 64 | count++; 65 | } 66 | } 67 | 68 | result = new address[](count); 69 | 70 | uint j = 0; 71 | for (uint8 i = 0; i < numAssets; i++) { 72 | if (isInAsset(assetsIn, i)) { 73 | result[j] = getAssetInfo(i).asset; 74 | j++; 75 | } 76 | } 77 | 78 | return result; 79 | } 80 | 81 | function accrue() external { 82 | accrueInternal(); 83 | } 84 | } -------------------------------------------------------------------------------- /contracts/test/CometHarnessInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "../CometInterface.sol"; 5 | 6 | abstract contract CometHarnessInterface is CometInterface { 7 | function accrue() virtual external; 8 | function getNow() virtual external view returns (uint40); 9 | function setNow(uint now_) virtual external; 10 | function setTotalsBasic(TotalsBasic memory totals) virtual external; 11 | function setTotalsCollateral(address asset, TotalsCollateral memory totals) virtual external; 12 | function setBasePrincipal(address account, int104 principal) virtual external; 13 | function setCollateralBalance(address account, address asset, uint128 balance) virtual external; 14 | function updateAssetsInExternal(address account, address asset, uint128 initialUserBalance, uint128 finalUserBalance) virtual external; 15 | function getAssetList(address account) virtual external view returns (address[] memory); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/test/CometModified.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "../Comet.sol"; 5 | 6 | /** 7 | * @title A modified version of Compound Comet 8 | * @notice This is solely used for testing upgrades 9 | * @author Compound 10 | */ 11 | contract CometModified is Comet { 12 | 13 | constructor(Configuration memory config) Comet(config) {} 14 | 15 | /** 16 | * @notice Initialize storage for a liquidator 17 | * @dev Solely used for testing upgrades 18 | */ 19 | function initialize(address liquidator) external { 20 | liquidatorPoints[liquidator].numAbsorbs = type(uint32).max; 21 | } 22 | 23 | function newFunction() external pure returns (int) { 24 | return 101; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/test/CometModifiedFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "./CometModified.sol"; 5 | import "../CometConfiguration.sol"; 6 | 7 | contract CometModifiedFactory is CometConfiguration { 8 | function clone(Configuration calldata config) external returns (address) { 9 | return address(new CometModified(config)); 10 | } 11 | } -------------------------------------------------------------------------------- /contracts/test/Dog.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | contract Dog { 5 | bool public initialized; 6 | string public name; 7 | Dog public father; 8 | Dog[] public pups; 9 | 10 | struct Puppers { 11 | uint index; 12 | Dog pup; 13 | } 14 | 15 | function initializeDog(string memory name_, Dog father_, Dog[] memory pups_) public { 16 | require(!initialized, "already initialized"); 17 | initialized = true; 18 | name = name_; 19 | father = father_; 20 | for (uint i = 0; i < pups_.length; i++) { 21 | pups.push(pups_[i]); 22 | } 23 | } 24 | 25 | constructor(string memory name_, Dog father_, Dog[] memory pups_) { 26 | initializeDog(name_, father_, pups_); 27 | } 28 | 29 | function addPup(Dog pup) public { 30 | pups.push(pup); 31 | } 32 | 33 | function puppers() public returns (Puppers[] memory) { 34 | Puppers[] memory puppers = new Puppers[](pups.length); 35 | for (uint i = 0; i < pups.length; i++) { 36 | puppers[i] = Puppers({ 37 | index: i, 38 | pup: pups[i] 39 | }); 40 | } 41 | return puppers; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/test/EvilToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "./../ERC20.sol"; 5 | import "./../Comet.sol"; 6 | import "./FaucetToken.sol"; 7 | 8 | /** 9 | * @title Malicious ERC20 token 10 | * @dev FaucetToken that attempts reentrancy attacks 11 | */ 12 | contract EvilToken is FaucetToken { 13 | enum AttackType { 14 | TRANSFER_FROM, 15 | WITHDRAW_FROM, 16 | SUPPLY_FROM 17 | } 18 | 19 | struct ReentryAttack { 20 | AttackType attackType; 21 | address source; 22 | address destination; 23 | address asset; 24 | uint amount; 25 | uint maxCalls; 26 | } 27 | 28 | ReentryAttack public attack; 29 | uint public numberOfCalls = 0; 30 | 31 | constructor( 32 | uint256 _initialAmount, 33 | string memory _tokenName, 34 | uint8 _decimalUnits, 35 | string memory _tokenSymbol 36 | ) FaucetToken(_initialAmount, _tokenName, _decimalUnits, _tokenSymbol) { 37 | attack = ReentryAttack({ 38 | attackType: AttackType.TRANSFER_FROM, 39 | source: address(this), 40 | destination: address(this), 41 | asset: address(this), 42 | amount: 1e6, 43 | maxCalls: type(uint).max 44 | }); 45 | } 46 | 47 | function getAttack() external view returns (ReentryAttack memory) { 48 | return attack; 49 | } 50 | 51 | function setAttack(ReentryAttack memory attack_) external { 52 | attack = attack_; 53 | } 54 | 55 | function transfer(address, uint256) external override returns (bool) { 56 | return performAttack(); 57 | } 58 | 59 | function transferFrom(address, address, uint256) external override returns (bool) { 60 | return performAttack(); 61 | } 62 | 63 | function performAttack() internal returns (bool) { 64 | ReentryAttack memory reentryAttack = attack; 65 | numberOfCalls++; 66 | if (numberOfCalls > reentryAttack.maxCalls) { 67 | // do nothing 68 | } else if (reentryAttack.attackType == AttackType.TRANSFER_FROM) { 69 | Comet(payable(msg.sender)).transferFrom( 70 | reentryAttack.source, 71 | reentryAttack.destination, 72 | reentryAttack.amount 73 | ); 74 | } else if (reentryAttack.attackType == AttackType.WITHDRAW_FROM) { 75 | Comet(payable(msg.sender)).withdrawFrom( 76 | reentryAttack.source, 77 | reentryAttack.destination, 78 | reentryAttack.asset, 79 | reentryAttack.amount 80 | ); 81 | } else if (reentryAttack.attackType == AttackType.SUPPLY_FROM) { 82 | Comet(payable(msg.sender)).supplyFrom( 83 | reentryAttack.source, 84 | reentryAttack.destination, 85 | reentryAttack.asset, 86 | reentryAttack.amount 87 | ); 88 | } else { 89 | revert("invalid reentry attack"); 90 | } 91 | return true; 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /contracts/test/FaucetToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @title Standard ERC20 token 6 | * @dev Implementation of the basic standard token. 7 | * See https://github.com/ethereum/EIPs/issues/20 8 | */ 9 | contract StandardToken { 10 | string public name; 11 | string public symbol; 12 | uint8 public decimals; 13 | uint256 public totalSupply; 14 | mapping (address => mapping (address => uint256)) public allowance; 15 | mapping(address => uint256) public balanceOf; 16 | event Approval(address indexed owner, address indexed spender, uint256 value); 17 | event Transfer(address indexed from, address indexed to, uint256 value); 18 | 19 | constructor(uint256 _initialAmount, string memory _tokenName, uint8 _decimalUnits, string memory _tokenSymbol) { 20 | totalSupply = _initialAmount; 21 | balanceOf[msg.sender] = _initialAmount; 22 | name = _tokenName; 23 | symbol = _tokenSymbol; 24 | decimals = _decimalUnits; 25 | } 26 | 27 | function transfer(address dst, uint256 amount) external virtual returns (bool) { 28 | require(amount <= balanceOf[msg.sender], "ERC20: transfer amount exceeds balance"); 29 | balanceOf[msg.sender] = balanceOf[msg.sender] - amount; 30 | balanceOf[dst] = balanceOf[dst] + amount; 31 | emit Transfer(msg.sender, dst, amount); 32 | return true; 33 | } 34 | 35 | function transferFrom(address src, address dst, uint256 amount) external virtual returns (bool) { 36 | require(amount <= allowance[src][msg.sender], "ERC20: transfer amount exceeds allowance"); 37 | require(amount <= balanceOf[src], "ERC20: transfer amount exceeds balance"); 38 | allowance[src][msg.sender] = allowance[src][msg.sender] - amount; 39 | balanceOf[src] = balanceOf[src] - amount; 40 | balanceOf[dst] = balanceOf[dst] + amount; 41 | emit Transfer(src, dst, amount); 42 | return true; 43 | } 44 | 45 | function approve(address _spender, uint256 amount) external returns (bool) { 46 | allowance[msg.sender][_spender] = amount; 47 | emit Approval(msg.sender, _spender, amount); 48 | return true; 49 | } 50 | } 51 | 52 | /** 53 | * @title The Compound Faucet Test Token 54 | * @author Compound 55 | * @notice A simple test token that lets anyone get more of it. 56 | */ 57 | contract FaucetToken is StandardToken { 58 | constructor(uint256 _initialAmount, string memory _tokenName, uint8 _decimalUnits, string memory _tokenSymbol) 59 | StandardToken(_initialAmount, _tokenName, _decimalUnits, _tokenSymbol) { 60 | } 61 | 62 | function allocateTo(address _owner, uint256 value) public { 63 | balanceOf[_owner] += value; 64 | totalSupply += value; 65 | emit Transfer(address(this), _owner, value); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/test/FaucetWETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "../vendor/canonical-weth/contracts/WETH9.sol"; 5 | 6 | /** 7 | * @title The faucet WETH Test Token 8 | * @author Compound 9 | * @notice A simple test token that lets anyone get more of it. 10 | */ 11 | contract FaucetWETH is WETH9 { 12 | constructor(uint256 _initialAmount, string memory _tokenName, uint8 _decimalUnits, string memory _tokenSymbol) WETH9() {} 13 | 14 | function allocateTo(address _owner, uint256 value) public { 15 | balanceOf[_owner] += value; 16 | emit Transfer(address(this), _owner, value); 17 | } 18 | } -------------------------------------------------------------------------------- /contracts/test/Fauceteer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "../ERC20.sol"; 5 | 6 | contract Fauceteer { 7 | /// @notice Mapping of user address -> asset address -> last time the user 8 | /// received that asset 9 | mapping(address => mapping(address => uint)) public lastReceived; 10 | 11 | /* errors */ 12 | error BalanceTooLow(); 13 | error RequestedTooFrequently(); 14 | error TransferFailed(); 15 | 16 | function drip(address token) public { 17 | uint balance = ERC20(token).balanceOf(address(this)); 18 | if (balance <= 0) revert BalanceTooLow(); 19 | 20 | if (block.timestamp - lastReceived[msg.sender][token] < 1 days) revert RequestedTooFrequently(); 21 | 22 | lastReceived[msg.sender][token] = block.timestamp; 23 | 24 | bool success = ERC20(token).transfer(msg.sender, balance / 10000); // 0.01% 25 | if (!success) revert TransferFailed(); 26 | } 27 | } -------------------------------------------------------------------------------- /contracts/test/SimplePriceFeed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity 0.8.15; 3 | 4 | import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; 5 | 6 | contract SimplePriceFeed is AggregatorV3Interface { 7 | int public price; 8 | 9 | uint8 public immutable override decimals; 10 | 11 | string public constant override description = "Mock Chainlink price aggregator"; 12 | 13 | uint public constant override version = 1; 14 | 15 | constructor(int initialPrice, uint8 decimals_) { 16 | price = initialPrice; 17 | decimals = decimals_; 18 | } 19 | 20 | function setPrice(int price_) public { 21 | price = price_; 22 | } 23 | 24 | function getRoundData(uint80 _roundId) override external view returns ( 25 | uint80 roundId, 26 | int256 answer, 27 | uint256 startedAt, 28 | uint256 updatedAt, 29 | uint80 answeredInRound 30 | ) { 31 | return (_roundId, price, 0, 0, 0); 32 | } 33 | 34 | function latestRoundData() override external view returns ( 35 | uint80 roundId, 36 | int256 answer, 37 | uint256 startedAt, 38 | uint256 updatedAt, 39 | uint80 answeredInRound 40 | ) { 41 | return (0, price, 0, 0, 0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface AggregatorV3Interface { 5 | function decimals() external view returns (uint8); 6 | 7 | function description() external view returns (string memory); 8 | 9 | function version() external view returns (uint256); 10 | 11 | // getRoundData and latestRoundData should both raise "No data present" 12 | // if they do not have data to report, instead of returning unset values 13 | // which could be misinterpreted as actual reported values. 14 | function getRoundData(uint80 _roundId) 15 | external 16 | view 17 | returns ( 18 | uint80 roundId, 19 | int256 answer, 20 | uint256 startedAt, 21 | uint256 updatedAt, 22 | uint80 answeredInRound 23 | ); 24 | 25 | function latestRoundData() 26 | external 27 | view 28 | returns ( 29 | uint80 roundId, 30 | int256 answer, 31 | uint256 startedAt, 32 | uint256 updatedAt, 33 | uint80 answeredInRound 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/vendor/access/Ownable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "../utils/Context.sol"; 7 | 8 | /** 9 | * @dev Contract module which provides a basic access control mechanism, where 10 | * there is an account (an owner) that can be granted exclusive access to 11 | * specific functions. 12 | * 13 | * By default, the owner account will be the one that deploys the contract. This 14 | * can later be changed with {transferOwnership}. 15 | * 16 | * This module is used through inheritance. It will make available the modifier 17 | * `onlyOwner`, which can be applied to your functions to restrict their use to 18 | * the owner. 19 | */ 20 | abstract contract Ownable is Context { 21 | address private _owner; 22 | 23 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 24 | 25 | /** 26 | * @dev Initializes the contract setting the deployer as the initial owner. 27 | */ 28 | constructor() { 29 | _transferOwnership(_msgSender()); 30 | } 31 | 32 | /** 33 | * @dev Returns the address of the current owner. 34 | */ 35 | function owner() public view virtual returns (address) { 36 | return _owner; 37 | } 38 | 39 | /** 40 | * @dev Throws if called by any account other than the owner. 41 | */ 42 | modifier onlyOwner() { 43 | require(owner() == _msgSender(), "Ownable: caller is not the owner"); 44 | _; 45 | } 46 | 47 | /** 48 | * @dev Leaves the contract without owner. It will not be possible to call 49 | * `onlyOwner` functions anymore. Can only be called by the current owner. 50 | * 51 | * NOTE: Renouncing ownership will leave the contract without an owner, 52 | * thereby removing any functionality that is only available to the owner. 53 | */ 54 | function renounceOwnership() public virtual onlyOwner { 55 | _transferOwnership(address(0)); 56 | } 57 | 58 | /** 59 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 60 | * Can only be called by the current owner. 61 | */ 62 | function transferOwnership(address newOwner) public virtual onlyOwner { 63 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 64 | _transferOwnership(newOwner); 65 | } 66 | 67 | /** 68 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 69 | * Internal function without access restriction. 70 | */ 71 | function _transferOwnership(address newOwner) internal virtual { 72 | address oldOwner = _owner; 73 | _owner = newOwner; 74 | emit OwnershipTransferred(oldOwner, newOwner); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /contracts/vendor/interfaces/draft-IERC1822.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified 8 | * proxy whose upgrades are fully controlled by the current implementation. 9 | */ 10 | interface IERC1822Proxiable { 11 | /** 12 | * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation 13 | * address. 14 | * 15 | * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks 16 | * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this 17 | * function revert if invoked through a proxy. 18 | */ 19 | function proxiableUUID() external view returns (bytes32); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/vendor/proxy/ERC1967/ERC1967Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "../Proxy.sol"; 7 | import "./ERC1967Upgrade.sol"; 8 | 9 | /** 10 | * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an 11 | * implementation address that can be changed. This address is stored in storage in the location specified by 12 | * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the 13 | * implementation behind the proxy. 14 | */ 15 | contract ERC1967Proxy is Proxy, ERC1967Upgrade { 16 | /** 17 | * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. 18 | * 19 | * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded 20 | * function call, and allows initializating the storage of the proxy like a Solidity constructor. 21 | */ 22 | constructor(address _logic, bytes memory _data) payable { 23 | assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); 24 | _upgradeToAndCall(_logic, _data, false); 25 | } 26 | 27 | /** 28 | * @dev Returns the current implementation address. 29 | */ 30 | function _implementation() internal view virtual override returns (address impl) { 31 | return ERC1967Upgrade._getImplementation(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/vendor/proxy/beacon/IBeacon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev This is the interface that {BeaconProxy} expects of its beacon. 8 | */ 9 | interface IBeacon { 10 | /** 11 | * @dev Must return an address that can be used as a delegate call target. 12 | * 13 | * {BeaconProxy} will check that this address is a contract. 14 | */ 15 | function implementation() external view returns (address); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/vendor/utils/Context.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Provides information about the current execution context, including the 8 | * sender of the transaction and its data. While these are generally available 9 | * via msg.sender and msg.data, they should not be accessed in such a direct 10 | * manner, since when dealing with meta-transactions the account sending and 11 | * paying for execution may not be the actual sender (as far as an application 12 | * is concerned). 13 | * 14 | * This contract is only required for intermediate, library-like contracts. 15 | */ 16 | abstract contract Context { 17 | function _msgSender() internal view virtual returns (address) { 18 | return msg.sender; 19 | } 20 | 21 | function _msgData() internal view virtual returns (bytes calldata) { 22 | return msg.data; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/vendor/utils/StorageSlot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Library for reading and writing primitive types to specific storage slots. 8 | * 9 | * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. 10 | * This library helps with reading and writing to such slots without the need for inline assembly. 11 | * 12 | * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. 13 | * 14 | * Example usage to set ERC1967 implementation slot: 15 | * ``` 16 | * contract ERC1967 { 17 | * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 18 | * 19 | * function _getImplementation() internal view returns (address) { 20 | * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; 21 | * } 22 | * 23 | * function _setImplementation(address newImplementation) internal { 24 | * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); 25 | * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; 26 | * } 27 | * } 28 | * ``` 29 | * 30 | * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ 31 | */ 32 | library StorageSlot { 33 | struct AddressSlot { 34 | address value; 35 | } 36 | 37 | struct BooleanSlot { 38 | bool value; 39 | } 40 | 41 | struct Bytes32Slot { 42 | bytes32 value; 43 | } 44 | 45 | struct Uint256Slot { 46 | uint256 value; 47 | } 48 | 49 | /** 50 | * @dev Returns an `AddressSlot` with member `value` located at `slot`. 51 | */ 52 | function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { 53 | assembly { 54 | r.slot := slot 55 | } 56 | } 57 | 58 | /** 59 | * @dev Returns an `BooleanSlot` with member `value` located at `slot`. 60 | */ 61 | function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { 62 | assembly { 63 | r.slot := slot 64 | } 65 | } 66 | 67 | /** 68 | * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. 69 | */ 70 | function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { 71 | assembly { 72 | r.slot := slot 73 | } 74 | } 75 | 76 | /** 77 | * @dev Returns an `Uint256Slot` with member `value` located at `slot`. 78 | */ 79 | function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { 80 | assembly { 81 | r.slot := slot 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /deployments/fuji/usdc/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compound USDC", 3 | "symbol": "cUSDCv3", 4 | "baseToken": "USDC", 5 | "baseTokenPriceFeed": "0x7898AcCC83587C3C55116c5230C17a6Cd9C71bad", 6 | "borrowMin": "1000e6", 7 | "storeFrontPriceFactor": 0.5, 8 | "targetReserves": "5000000e6", 9 | "rates": { 10 | "supplyKink": 0.8, 11 | "supplySlopeLow": 0.0325, 12 | "supplySlopeHigh": 0.4, 13 | "supplyBase": 0, 14 | "borrowKink": 0.8, 15 | "borrowSlopeLow": 0.035, 16 | "borrowSlopeHigh": 0.25, 17 | "borrowBase": 0.015 18 | }, 19 | "tracking": { 20 | "indexScale": "1e15", 21 | "baseSupplySpeed": "0.000011574074074074073e15", 22 | "baseBorrowSpeed": "0.0011458333333333333e15", 23 | "baseMinForRewards": "1000000e6" 24 | }, 25 | "assets": { 26 | "WBTC.e": { 27 | "priceFeed": "0x31CF013A08c6Ac228C94551d535d5BAfE19c602a", 28 | "decimals": "8", 29 | "borrowCF": 0.7, 30 | "liquidateCF": 0.75, 31 | "liquidationFactor": 0.93, 32 | "supplyCap": "35000e8" 33 | }, 34 | "WAVAX": { 35 | "priceFeed": "0x5498BB86BC934c8D34FDA08E81D444153d0D06aD", 36 | "decimals": "18", 37 | "borrowCF": 0.82, 38 | "liquidateCF": 0.85, 39 | "liquidationFactor": 0.93, 40 | "supplyCap": "1000000e18" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /deployments/fuji/usdc/roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "comet": "0x59BF4753899C20EA152dEefc6f6A14B2a5CC3021", 3 | "configurator": "0x8c083632099CBA949EA61A3044DB1B5A27818b20", 4 | "rewards": "0x7CA364f9C4257FE2E22d503dD0E3f1c1Db41591d", 5 | "fauceteer": "0x45D3465046B72D319ef0090b431678b160B1e628" 6 | } -------------------------------------------------------------------------------- /deployments/goerli/usdc/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compound USDC", 3 | "symbol": "cUSDCv3", 4 | "baseToken": "USDC", 5 | "baseTokenPriceFeed": "0xAb5c49580294Aff77670F839ea425f5b78ab3Ae7", 6 | "borrowMin": "1000e6", 7 | "storeFrontPriceFactor": 0.5, 8 | "targetReserves": "5000000e6", 9 | "rates": { 10 | "supplyKink": 0.8, 11 | "supplySlopeLow": 0.0325, 12 | "supplySlopeHigh": 0.4, 13 | "supplyBase": 0, 14 | "borrowKink": 0.8, 15 | "borrowSlopeLow": 0.035, 16 | "borrowSlopeHigh": 0.25, 17 | "borrowBase": 0.015 18 | }, 19 | "tracking": { 20 | "indexScale": "1e15", 21 | "baseSupplySpeed": "0.000011574074074074073e15", 22 | "baseBorrowSpeed": "0.0011458333333333333e15", 23 | "baseMinForRewards": "10000e6" 24 | }, 25 | "rewardToken": "COMP", 26 | "assets": { 27 | "COMP": { 28 | "priceFeed": "0x54a06047087927D9B0fb21c1cf0ebd792764dDB8", 29 | "decimals": "18", 30 | "borrowCF": 0.65, 31 | "liquidateCF": 0.7, 32 | "liquidationFactor": 0.92, 33 | "supplyCap": "500000e18" 34 | }, 35 | "WBTC": { 36 | "priceFeed": "0xA39434A63A52E749F02807ae27335515BA4b07F7", 37 | "decimals": "8", 38 | "borrowCF": 0.7, 39 | "liquidateCF": 0.75, 40 | "liquidationFactor": 0.93, 41 | "supplyCap": "35000e8" 42 | }, 43 | "WETH": { 44 | "priceFeed": "0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e", 45 | "decimals": "18", 46 | "borrowCF": 0.82, 47 | "liquidateCF": 0.85, 48 | "liquidationFactor": 0.93, 49 | "supplyCap": "1000000e18" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /deployments/goerli/usdc/relations.ts: -------------------------------------------------------------------------------- 1 | import { RelationConfigMap } from '../../../plugins/deployment_manager/RelationConfig'; 2 | import baseRelationConfig from '../../relations'; 3 | 4 | export default { 5 | ...baseRelationConfig, 6 | 'fxRoot': { 7 | relations: { 8 | stateSender: { 9 | field: async (fxRoot) => fxRoot.stateSender() 10 | } 11 | } 12 | } 13 | }; -------------------------------------------------------------------------------- /deployments/goerli/usdc/roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "timelock": "0x8Fa336EB4bF58Cfc508dEA1B0aeC7336f55B1399", 3 | "fauceteer": "0x75442Ac771a7243433e033F3F8EaB2631e22938f", 4 | "rewards": "0xef9e070044d62C38D2e316146dDe92AD02CF2c2c", 5 | "comet": "0x3EE77595A8459e93C2888b13aDB354017B198188", 6 | "configurator": "0xB28495db3eC65A0e3558F040BC4f98A0d588Ae60", 7 | "bulker": "0xf82AAB8ae0E7F6a2ecBfe2375841d83AeA4cb9cE", 8 | "fxRoot": "0x3d1d3e34f7fb6d26245e6640e1c50710efff15ba" 9 | } -------------------------------------------------------------------------------- /deployments/hardhat/dai/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compound DAI", 3 | "symbol": "cDAIv3", 4 | "baseToken": "DAI", 5 | "borrowMin": "1000e6", 6 | "storeFrontPriceFactor": 0.5, 7 | "targetReserves": "5000000e6", 8 | "rates": { 9 | "supplyKink": 0.8, 10 | "supplySlopeLow": 0.0325, 11 | "supplySlopeHigh": 0.4, 12 | "supplyBase": 0, 13 | "borrowKink": 0.8, 14 | "borrowSlopeLow": 0.035, 15 | "borrowSlopeHigh": 0.25, 16 | "borrowBase": 0.015 17 | }, 18 | "tracking": { 19 | "indexScale": "1e15", 20 | "baseSupplySpeed": "0.000011574074074074073e15", 21 | "baseBorrowSpeed": "0.0011458333333333333e15", 22 | "baseMinForRewards": "1000000e6" 23 | }, 24 | "rewardToken": "GOLD", 25 | "assets": {} 26 | } 27 | -------------------------------------------------------------------------------- /deployments/kovan/usdc/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compound USDC", 3 | "symbol": "cUSDCv3", 4 | "baseToken": "USDC", 5 | "baseTokenPriceFeed": "0x9211c6b3BF41A10F78539810Cf5c64e1BB78Ec60", 6 | "borrowMin": "1000e6", 7 | "storeFrontPriceFactor": 0.5, 8 | "targetReserves": "5000000e6", 9 | "rates": { 10 | "supplyKink": 0.8, 11 | "supplySlopeLow": 0.0325, 12 | "supplySlopeHigh": 0.4, 13 | "supplyBase": 0, 14 | "borrowKink": 0.8, 15 | "borrowSlopeLow": 0.035, 16 | "borrowSlopeHigh": 0.25, 17 | "borrowBase": 0.015 18 | }, 19 | "tracking": { 20 | "indexScale": "1e15", 21 | "baseSupplySpeed": "0.000011574074074074073e15", 22 | "baseBorrowSpeed": "0.0011458333333333333e15", 23 | "baseMinForRewards": "1000000e6" 24 | }, 25 | "rewardToken": "COMP", 26 | "assets": { 27 | "COMP": { 28 | "priceFeed": "0xECF93D14d25E02bA2C13698eeDca9aA98348EFb6", 29 | "decimals": "18", 30 | "borrowCF": 0.65, 31 | "liquidateCF": 0.7, 32 | "liquidationFactor": 0.92, 33 | "supplyCap": "500000e18" 34 | }, 35 | "WBTC": { 36 | "priceFeed": "0x6135b13325bfC4B00278B4abC5e20bbce2D6580e", 37 | "decimals": "8", 38 | "borrowCF": 0.7, 39 | "liquidateCF": 0.75, 40 | "liquidationFactor": 0.93, 41 | "supplyCap": "35000e8" 42 | }, 43 | "WETH": { 44 | "priceFeed": "0x9326BFA02ADD2366b30bacB125260Af641031331", 45 | "decimals": "18", 46 | "borrowCF": 0.82, 47 | "liquidateCF": 0.85, 48 | "liquidationFactor": 0.93, 49 | "supplyCap": "1000000e18" 50 | }, 51 | "UNI": { 52 | "priceFeed": "0xDA5904BdBfB4EF12a3955aEcA103F51dc87c7C39", 53 | "decimals": "18", 54 | "borrowCF": 0.75, 55 | "liquidateCF": 0.8, 56 | "liquidationFactor": 0.92, 57 | "supplyCap": "50000000e18" 58 | }, 59 | "LINK": { 60 | "priceFeed": "0x396c5E36DD0a0F5a5D33dae44368D4193f69a1F0", 61 | "decimals": "18", 62 | "borrowCF": 0.75, 63 | "liquidateCF": 0.8, 64 | "liquidationFactor": 0.92, 65 | "supplyCap": "50000000e18" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /deployments/kovan/usdc/roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "comet": "0xa7D85950E6E1bB7115c626cBB388Fa0a8C927c1c", 3 | "configurator": "0xDb3c6Ae44FE4689f142Ed8dE1a87304249d3d5a6", 4 | "rewards": "0xC694877D91A8aEfb9D95cf34393cdC0DDdAded18", 5 | "fauceteer": "0xA42d20100750b4E4d777bD067134423C0775B869", 6 | "bulker": "0x9446B268Ad5fa8446193A1B3d4071A7734f92e81" 7 | } -------------------------------------------------------------------------------- /deployments/mainnet/usdc/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compound USDC", 3 | "symbol": "cUSDCv3", 4 | "baseToken": "USDC", 5 | "baseTokenAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 6 | "baseTokenPriceFeed": "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6", 7 | "borrowMin": "100e6", 8 | "governor": "0x6d903f6003cca6255d85cca4d3b5e5146dc33925", 9 | "pauseGuardian": "0xbbf3f1421d886e9b2c5d716b5192ac998af2012c", 10 | "storeFrontPriceFactor": 0.5, 11 | "targetReserves": "5000000e6", 12 | "rates": { 13 | "supplyKink": 0.8, 14 | "supplySlopeLow": 0.0325, 15 | "supplySlopeHigh": 0.4, 16 | "supplyBase": 0, 17 | "borrowKink": 0.8, 18 | "borrowSlopeLow": 0.035, 19 | "borrowSlopeHigh": 0.25, 20 | "borrowBase": 0.015 21 | }, 22 | "tracking": { 23 | "indexScale": "1e15", 24 | "baseSupplySpeed": "0e15", 25 | "baseBorrowSpeed": "0e15", 26 | "baseMinForRewards": "1000000e6" 27 | }, 28 | "rewardTokenAddress": "0xc00e94cb662c3520282e6f5717214004a7f26888", 29 | "assets": { 30 | "COMP": { 31 | "address": "0xc00e94cb662c3520282e6f5717214004a7f26888", 32 | "priceFeed": "0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5", 33 | "decimals": "18", 34 | "borrowCF": 0.65, 35 | "liquidateCF": 0.70, 36 | "liquidationFactor": 0.93, 37 | "supplyCap": "0e18" 38 | }, 39 | "WBTC": { 40 | "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", 41 | "priceFeed": "0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c", 42 | "decimals": "8", 43 | "borrowCF": 0.70, 44 | "liquidateCF": 0.77, 45 | "liquidationFactor": 0.95, 46 | "supplyCap": "0e8" 47 | }, 48 | "WETH": { 49 | "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 50 | "priceFeed": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", 51 | "decimals": "18", 52 | "borrowCF": 0.825, 53 | "liquidateCF": 0.895, 54 | "liquidationFactor": 0.95, 55 | "supplyCap": "0e18" 56 | }, 57 | "UNI": { 58 | "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", 59 | "priceFeed": "0x553303d460EE0afB37EdFf9bE42922D8FF63220e", 60 | "decimals": "18", 61 | "borrowCF": 0.75, 62 | "liquidateCF": 0.81, 63 | "liquidationFactor": 0.93, 64 | "supplyCap": "0e18" 65 | }, 66 | "LINK": { 67 | "address": "0x514910771af9ca656af840dff83e8264ecf986ca", 68 | "priceFeed": "0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c", 69 | "decimals": "18", 70 | "borrowCF": 0.79, 71 | "liquidateCF": 0.85, 72 | "liquidationFactor": 0.93, 73 | "supplyCap": "0e18" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /deployments/mainnet/usdc/deploy.ts: -------------------------------------------------------------------------------- 1 | import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager'; 2 | import { DeploySpec, deployComet } from '../../../src/deploy'; 3 | 4 | export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise { 5 | const USDC = await deploymentManager.existing('USDC', '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'); 6 | const WBTC = await deploymentManager.existing('WBTC', '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'); 7 | const WETH = await deploymentManager.existing('WETH', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'); 8 | const COMP = await deploymentManager.existing('COMP', '0xc00e94cb662c3520282e6f5717214004a7f26888'); 9 | const LINK = await deploymentManager.existing('LINK', '0x514910771af9ca656af840dff83e8264ecf986ca'); 10 | const UNI = await deploymentManager.existing('UNI', '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'); 11 | 12 | // Deploy all Comet-related contracts 13 | const deployed = await deployComet(deploymentManager, deploySpec); 14 | const { comet } = deployed; 15 | 16 | // Deploy Bulker 17 | const bulker = await deploymentManager.deploy( 18 | 'bulker', 19 | 'Bulker.sol', 20 | [await comet.governor(), WETH.address] 21 | ); 22 | 23 | return { ...deployed, bulker }; 24 | } 25 | -------------------------------------------------------------------------------- /deployments/mainnet/usdc/roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "comptrollerV2": "0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b", 3 | "comet": "0xc3d688B66703497DAA19211EEdff47f25384cdc3", 4 | "configurator": "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3", 5 | "rewards": "0x1B0e765F6224C21223AeA2af16c1C46E38885a40", 6 | "bulker": "0x74a81F84268744a40FEBc48f8b812a1f188D80C3" 7 | } -------------------------------------------------------------------------------- /deployments/mumbai/usdc/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compound USDC", 3 | "symbol": "cUSDCv3", 4 | "baseToken": "USDC", 5 | "baseTokenPriceFeed": "0x572dDec9087154dC5dfBB1546Bb62713147e0Ab0", 6 | "borrowMin": "100e6", 7 | "storeFrontPriceFactor": 0.5, 8 | "targetReserves": "5000000e6", 9 | "rates": { 10 | "supplyKink": 0.8, 11 | "supplySlopeLow": 0.0325, 12 | "supplySlopeHigh": 0.4, 13 | "supplyBase": 0, 14 | "borrowKink": 0.8, 15 | "borrowSlopeLow": 0.035, 16 | "borrowSlopeHigh": 0.25, 17 | "borrowBase": 0.015 18 | }, 19 | "tracking": { 20 | "indexScale": "1e15", 21 | "baseSupplySpeed": "0.000011574074074074073e15", 22 | "baseBorrowSpeed": "0.0011458333333333333e15", 23 | "baseMinForRewards": "10000e6" 24 | }, 25 | "assets": { 26 | "DAI": { 27 | "priceFeed": "0x0FCAa9c899EC5A91eBc3D5Dd869De833b06fB046", 28 | "decimals": "18", 29 | "borrowCF": 0.79, 30 | "liquidateCF": 0.85, 31 | "liquidationFactor": 0.93, 32 | "supplyCap": "500000e18" 33 | }, 34 | "WETH": { 35 | "priceFeed": "0x0715A7794a1dc8e42615F059dD6e406A6594651A", 36 | "decimals": "18", 37 | "borrowCF": 0.82, 38 | "liquidateCF": 0.85, 39 | "liquidationFactor": 0.93, 40 | "supplyCap": "1000000e18" 41 | }, 42 | "WBTC": { 43 | "priceFeed": "0x007A22900a3B98143368Bd5906f8E17e9867581b", 44 | "decimals": "8", 45 | "borrowCF": 0.7, 46 | "liquidateCF": 0.75, 47 | "liquidationFactor": 0.93, 48 | "supplyCap": "35000e8" 49 | }, 50 | "WMATIC": { 51 | "priceFeed": "0xd0D5e3DB44DE05E9F294BB0a3bEEaF030DE24Ada", 52 | "decimals": "18", 53 | "borrowCF": 0.82, 54 | "liquidateCF": 0.85, 55 | "liquidationFactor": 0.93, 56 | "supplyCap": "1000000e18" 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /deployments/mumbai/usdc/relations.ts: -------------------------------------------------------------------------------- 1 | import { RelationConfigMap } from '../../../plugins/deployment_manager/RelationConfig'; 2 | import baseRelationConfig from '../../relations'; 3 | 4 | export default { 5 | ...baseRelationConfig, 6 | 'governor': { 7 | artifact: 'contracts/bridges/polygon/PolygonBridgeReceiver.sol:PolygonBridgeReceiver', 8 | } 9 | }; -------------------------------------------------------------------------------- /diagrams/configurator_diagram.uml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | rectangle GovSimple 4 | rectangle Timelock 5 | rectangle ProxyAdmin 6 | rectangle ConfiguratorProxy 7 | rectangle ConfiguratorImpl 8 | rectangle CometFactory 9 | rectangle CometProxy 10 | rectangle CometImpl 11 | rectangle Bulker 12 | 13 | GovSimple --> Timelock : admin 14 | Timelock --> ProxyAdmin : admin 15 | Timelock ~~> ConfiguratorImpl : governor 16 | Timelock ~~> CometImpl : governor 17 | ProxyAdmin --> ConfiguratorProxy : admin 18 | ProxyAdmin --> CometProxy : admin 19 | ConfiguratorProxy --> ConfiguratorImpl : impl 20 | ConfiguratorImpl --> CometFactory 21 | CometFactory ..> CometImpl : makes 22 | CometProxy --> CometImpl : impl 23 | Bulker --> CometProxy 24 | 25 | @enduml -------------------------------------------------------------------------------- /diagrams/inheritance_diagram.uml: -------------------------------------------------------------------------------- 1 | @startuml 2 | rectangle Comet 3 | rectangle CometConfiguration 4 | rectangle CometCore 5 | rectangle CometExt 6 | rectangle CometFactory 7 | rectangle CometInterface 8 | rectangle CometMath 9 | rectangle CometStorage 10 | rectangle ERC20 11 | 12 | Comet --> CometCore 13 | CometCore --> CometConfiguration 14 | CometCore --> CometStorage 15 | CometCore --> CometMath 16 | CometExt --> CometCore 17 | CometFactory --> CometConfiguration 18 | CometInterface --> CometCore 19 | CometInterface --> ERC20 20 | 21 | Comet ~~> CometExt : delegatecall 22 | @enduml -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Compound III Documentation 2 | 3 | ### Website 4 | 5 | Documentation website: 6 | https://docs.compound.finance/ 7 | 8 | 9 | ### Source Code 10 | 11 | The Compound III docs are now open source! They are hosted in a separate GitHub repository with GitHub pages. 12 | 13 | To edit the documentation website, fork the [docs site repository](https://github.com/compound-finance/compound-finance.github.io) and make a pull request in that repository. 14 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/comet/8ac17412ff4a144eca1c35846d416bf4cd28a32f/index.ts -------------------------------------------------------------------------------- /plugins/deployment_manager/Aliases.ts: -------------------------------------------------------------------------------- 1 | import { Address, Alias } from './Types'; 2 | import { Cache } from './Cache'; 3 | 4 | export type Aliases = Map; 5 | export type InvertedAliases = Map; 6 | 7 | // File to store aliases in, e.g. `$pwd/deployments/deployment/aliases.json` 8 | let aliasesSpec = { rel: 'aliases.json' }; 9 | 10 | // Read aliases 11 | export async function getAliases(cache: Cache): Promise { 12 | return await cache.readMap
(aliasesSpec); 13 | } 14 | 15 | // Stores aliases 16 | export async function storeAliases(cache: Cache, aliases: Aliases) { 17 | await cache.storeMap(aliasesSpec, aliases); 18 | } 19 | 20 | export async function putAlias(cache: Cache, alias: Alias, address: Address) { 21 | let aliases = await getAliases(cache); 22 | aliases.set(alias, address); 23 | await storeAliases(cache, aliases); 24 | } 25 | 26 | // Returns an inverted alias map where you can look up a list of aliases from an address 27 | export async function getInvertedAliases(cache: Cache): Promise { 28 | let aliases = await getAliases(cache); 29 | let inverted = new Map(); 30 | for (let [alias, address] of aliases.entries()) { 31 | let addressLower = address.toLowerCase(); 32 | let previous = inverted.get(addressLower) ?? []; 33 | inverted.set(addressLower, [...previous, alias]); 34 | } 35 | return inverted; 36 | } 37 | -------------------------------------------------------------------------------- /plugins/deployment_manager/ContractMap.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from 'ethers'; 2 | import { Cache, FileSpec } from './Cache'; 3 | import { Address, Alias, BuildFile } from './Types'; 4 | 5 | export type ContractMap = Map; 6 | 7 | function getFileSpec(network: string, address: Address): FileSpec { 8 | return { top: [network, '.contracts', address + '.json'] }; 9 | } 10 | 11 | export async function getBuildFile(cache: Cache, network: string, address: Address): Promise { 12 | return cache.readCache(getFileSpec(network, address)); 13 | } 14 | 15 | export async function storeBuildFile(cache: Cache, network: string, address: Address, buildFile: BuildFile) { 16 | await cache.storeCache(getFileSpec(network, address), buildFile); 17 | } 18 | -------------------------------------------------------------------------------- /plugins/deployment_manager/Enacted.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentManager } from './DeploymentManager'; 2 | import * as ts from 'typescript'; 3 | import * as fs from 'fs/promises'; 4 | import { Migration } from './Migration'; 5 | 6 | export async function writeEnacted(migration: Migration, deploymentManager: DeploymentManager, writeToFile: boolean = true): Promise { 7 | const network = deploymentManager.network; 8 | const deployment = deploymentManager.deployment; 9 | const migrationPath = `./deployments/${network}/${deployment}/migrations/${migration.name}.ts`; 10 | const program = ts.createProgram([migrationPath], { allowJs: true }); 11 | const sourceFile = program.getSourceFile(migrationPath); 12 | const newSourceCode = addEnactedToMigration(sourceFile); 13 | 14 | if (writeToFile) { 15 | const trace = deploymentManager.tracer(); 16 | await fs.writeFile(migrationPath, newSourceCode); 17 | trace(`Wrote \`enacted\` to migration at: ${migrationPath}`); 18 | } 19 | 20 | return newSourceCode; 21 | } 22 | 23 | export function addEnactedToMigration(sourceFile: ts.SourceFile): string { 24 | // Note: Another approach is to directly modify the AST, but unfortunately this does not 25 | // preserve the original formatting of the source code 26 | // Example of the AST approach in commit 73e60480627230d84bb40ab0269722a3e839713a 27 | const sourceFileText = sourceFile.getFullText(); 28 | const exportAssignment = sourceFile.statements.find(ts.isExportAssignment)!; 29 | const callExpression = exportAssignment.expression as ts.CallExpression; 30 | const objectLiteralExpression = callExpression.arguments.find(x => x.kind === ts.SyntaxKind.ObjectLiteralExpression) as ts.ObjectLiteralExpression; 31 | const enact = objectLiteralExpression.properties.find(x => (x.name as ts.Identifier).escapedText == 'enact')!; 32 | const enacted = objectLiteralExpression.properties.find(x => (x.name as ts.Identifier).escapedText == 'enacted'); 33 | let code = 34 | `\n\n async enacted(deploymentManager: DeploymentManager): Promise {\n return true;\n },`; 35 | let newSourceCode; 36 | if (enacted) { 37 | // If enacted already exists, just replace it 38 | let endPos = enacted.end; 39 | if (sourceFileText.charAt(enacted.end) === ',') { 40 | // Skip the original comma to avoid double commas 41 | endPos = enacted.end + 1; 42 | } 43 | newSourceCode = sourceFileText.substring(0, enacted.pos) 44 | + code 45 | + sourceFileText.substring(endPos); 46 | } else { 47 | let insertPos; 48 | if (sourceFileText.charAt(enact.end) === ',') { 49 | // Insert after the comma 50 | insertPos = enact.end + 1; 51 | } else { 52 | // Prepend a comma 53 | insertPos = enact.end; 54 | code = ',' + code; 55 | } 56 | newSourceCode = sourceFileText.substring(0, insertPos) 57 | + code 58 | + sourceFileText.substring(insertPos); 59 | } 60 | 61 | return newSourceCode; 62 | } -------------------------------------------------------------------------------- /plugins/deployment_manager/Migration.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentManager } from './DeploymentManager'; 2 | import { FileSpec } from './Cache'; 3 | 4 | export interface Actions { 5 | prepare: (dm: DeploymentManager) => Promise; 6 | enact: (dm: DeploymentManager, t: T) => Promise; 7 | enacted?: (dm: DeploymentManager) => Promise; 8 | verify?: (dm: DeploymentManager) => Promise; 9 | } 10 | 11 | export class Migration { 12 | name: string; 13 | actions: Actions; 14 | 15 | constructor(name: string, actions: Actions) { 16 | this.name = name; 17 | this.actions = actions; 18 | } 19 | } 20 | 21 | export async function loadMigration(path: string): Promise> { 22 | const { default: thing } = await import(path); 23 | if (!(thing instanceof Migration)) 24 | throw new Error(`Does not export a valid default Migration`); 25 | return thing; 26 | } 27 | 28 | export async function loadMigrations(paths: string[]): Promise[]> { 29 | const migrations = []; 30 | for (const path of paths) { 31 | migrations.push(await loadMigration(path)); 32 | } 33 | return migrations; 34 | } 35 | 36 | export function migration(name: string, actions: Actions) { 37 | return new Migration(name, actions); 38 | } 39 | 40 | export function getArtifactSpec(migration: Migration): FileSpec { 41 | return { rel: ['artifacts', `${migration.name}.json`] }; 42 | } 43 | -------------------------------------------------------------------------------- /plugins/deployment_manager/MigrationTemplate.ts: -------------------------------------------------------------------------------- 1 | import { Cache } from './Cache'; 2 | 3 | export interface MigrationTemplateVars { 4 | timestamp: number; 5 | name: string; 6 | } 7 | 8 | export function migrationTemplate({ timestamp, name }: MigrationTemplateVars): string { 9 | return `import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; 10 | import { migration } from '../../../../plugins/deployment_manager/Migration'; 11 | 12 | interface Vars {}; 13 | 14 | export default migration('${timestamp}_${name}', { 15 | prepare: async (deploymentManager: DeploymentManager) => { 16 | return {}; 17 | }, 18 | 19 | enact: async (governanceDeploymentManager: DeploymentManager, vars: Vars) => { 20 | // No governance changes 21 | } 22 | });\n`; 23 | } 24 | 25 | export function migrationName({ timestamp, name }: MigrationTemplateVars): string { 26 | return `${timestamp}_${name}.ts`; 27 | } 28 | 29 | function now(): number { 30 | return Math.floor(Date.now() / 1000); 31 | } 32 | 33 | export async function generateMigration( 34 | cache: Cache, 35 | name: string, 36 | timestamp?: number 37 | ): Promise { 38 | let templateVars: MigrationTemplateVars = { name, timestamp: timestamp ?? now() }; 39 | let migrationFileName = migrationName(templateVars); 40 | let migrationFileSpec = { rel: ['migrations', migrationFileName] }; 41 | 42 | if (await cache.readCache(migrationFileSpec, (x) => x) !== undefined) { 43 | throw new Error(`Migration ${migrationFileName} already exists.`); 44 | } 45 | 46 | await cache.storeCache( 47 | migrationFileSpec, 48 | migrationTemplate(templateVars), 49 | (x) => x.toString() 50 | ); 51 | 52 | return migrationFileName; 53 | } 54 | -------------------------------------------------------------------------------- /plugins/deployment_manager/NonceManager.ts: -------------------------------------------------------------------------------- 1 | import { NonceManager } from '@ethersproject/experimental'; 2 | import { TransactionRequest, TransactionResponse } from '@ethersproject/abstract-provider'; 3 | import { TypedDataDomain, TypedDataField, TypedDataSigner } from '@ethersproject/abstract-signer'; 4 | import { _TypedDataEncoder } from '@ethersproject/hash'; 5 | import { Deferrable } from '@ethersproject/properties'; 6 | import { providers } from 'ethers'; 7 | 8 | // NonceManager does not implement `_signTypedData`, which is needed for the EIP-712 functions 9 | export class ExtendedNonceManager extends NonceManager implements TypedDataSigner { 10 | async _reset() { 11 | return this.setTransactionCount(await this.getTransactionCount()); 12 | } 13 | 14 | async _signTypedData(domain: TypedDataDomain, types: Record>, value: Record): Promise { 15 | const provider = this.provider as providers.JsonRpcProvider; 16 | 17 | // Populate any ENS names (in-place) 18 | const populated = await _TypedDataEncoder.resolveNames(domain, types, value, (name: string) => { 19 | return provider.resolveName(name); 20 | }); 21 | 22 | const address = await this.getAddress(); 23 | 24 | return await provider.send('eth_signTypedData_v4', [ 25 | address.toLowerCase(), 26 | JSON.stringify(_TypedDataEncoder.getPayload(populated.domain, types, populated.value)) 27 | ]); 28 | } 29 | 30 | async sendTransaction(transaction: Deferrable): Promise { 31 | return super.sendTransaction(transaction).catch((e) => { 32 | this._reset(); 33 | throw(e); 34 | }); 35 | } 36 | } -------------------------------------------------------------------------------- /plugins/deployment_manager/Roots.ts: -------------------------------------------------------------------------------- 1 | import { Alias, Address } from './Types'; 2 | import { Cache } from './Cache'; 3 | 4 | export type Roots = Map; 5 | 6 | // File to store root information in, e.g. `$pwd/deployments/$network/$deployment/roots.json` 7 | let rootsSpec = { rel: 'roots.json' }; 8 | 9 | // Reads root information for given deployment 10 | export async function getRoots(cache: Cache): Promise { 11 | return await cache.readMap
(rootsSpec); 12 | } 13 | 14 | // Stores new roots for a given deployment in cache 15 | export async function putRoots(cache: Cache, roots: Roots): Promise { 16 | await cache.storeMap(rootsSpec, roots); 17 | return roots; 18 | } 19 | -------------------------------------------------------------------------------- /plugins/deployment_manager/Types.ts: -------------------------------------------------------------------------------- 1 | import { TransactionResponse } from '@ethersproject/abstract-provider'; 2 | 3 | export type ABI = string | any[]; 4 | export type Address = string; 5 | export type Alias = string; 6 | 7 | export interface BuildFile { 8 | contract: string; 9 | contracts: { 10 | [fileContractName: string]: ContractMetadata | { [contractName: string]: ContractMetadata }; 11 | }; 12 | version: string; 13 | } 14 | 15 | export interface ContractMetadata { 16 | network?: string; 17 | address: Address; 18 | name: string; 19 | abi: ABI; 20 | bin: string; 21 | metadata: string; 22 | source?: string; 23 | constructorArgs: string; 24 | } 25 | 26 | export type TraceArg = string | TransactionResponse; 27 | export type TraceFn = (TraceArg, ...any) => void; 28 | -------------------------------------------------------------------------------- /plugins/deployment_manager/Verify.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from 'hardhat/types'; 2 | import { Contract } from 'ethers'; 3 | 4 | import { manualVerifyContract } from './ManualVerify'; 5 | import { BuildFile } from './Types'; 6 | import { debug } from './Utils'; 7 | 8 | export type VerifyArgs = 9 | | { via: 'artifacts', address: string, constructorArguments: any } 10 | | { via: 'buildfile', contract: Contract, buildFile: BuildFile, deployArgs: any[] }; 11 | 12 | export type VerificationStrategy = 'none' | 'eager' | 'lazy'; 13 | 14 | export async function verifyContract( 15 | verifyArgs: VerifyArgs, 16 | hre: HardhatRuntimeEnvironment, 17 | raise = false, 18 | retries = 10 19 | ) { 20 | let address; 21 | try { 22 | if (verifyArgs.via === 'artifacts') { 23 | address = verifyArgs.address; 24 | await hre.run('verify:verify', { 25 | address: verifyArgs.address, 26 | constructorArguments: verifyArgs.constructorArguments, 27 | }); 28 | } else if (verifyArgs.via === 'buildfile') { 29 | address = verifyArgs.contract.address; 30 | await manualVerifyContract( 31 | verifyArgs.contract, 32 | verifyArgs.buildFile, 33 | verifyArgs.deployArgs, 34 | hre 35 | ); 36 | } else { 37 | throw new Error(`Unknown verfication via`); 38 | } 39 | debug('Contract at address ' + address + ' verified on Etherscan.'); 40 | } catch (e) { 41 | if (e.message.match(/Already Verified/i)) { 42 | debug('Contract at address ' + address + ' is already verified on Etherscan'); 43 | return; 44 | } else if (e.message.match(/does not have bytecode/i) && retries > 0) { 45 | debug('Waiting for ' + address + ' to propagate to Etherscan'); 46 | await new Promise((resolve) => setTimeout(resolve, 5000)); 47 | return await verifyContract(verifyArgs, hre, raise, retries - 1); 48 | } else { 49 | if (raise) { 50 | throw e; 51 | } else { 52 | console.error(`Unable to verify contract at ${address}: ${e}`); 53 | console.error(`Continuing on anyway...`); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /plugins/deployment_manager/VerifyArgs.ts: -------------------------------------------------------------------------------- 1 | import { Address, Alias } from './Types'; 2 | import { Cache } from './Cache'; 3 | import { VerifyArgs } from './Verify'; 4 | 5 | export type VerifyArgsMap = Map; 6 | export type InvertedAliases = Map; 7 | 8 | // File to store verification metadata in, e.g. `$pwd/deployments/deployment/verify/args.json` 9 | let verificationSpec = { rel: ['verify', 'args.json'] }; 10 | 11 | // Read verify args 12 | export async function getVerifyArgs(cache: Cache): Promise { 13 | return await cache.readMap(verificationSpec); 14 | } 15 | 16 | // Stores verify args 17 | export async function storeVerifyArgs(cache: Cache, verifyArgsMap: VerifyArgsMap) { 18 | await cache.storeMap(verificationSpec, verifyArgsMap); 19 | } 20 | 21 | export async function putVerifyArgs(cache: Cache, address: Address, verifyArgs: VerifyArgs) { 22 | let verifyArgsMap = await getVerifyArgs(cache); 23 | verifyArgsMap.set(address, verifyArgs); 24 | await storeVerifyArgs(cache, verifyArgsMap); 25 | } 26 | 27 | export async function deleteVerifyArgs(cache: Cache, address: Address) { 28 | let verifyArgsMap = await getVerifyArgs(cache); 29 | verifyArgsMap.delete(address); 30 | await storeVerifyArgs(cache, verifyArgsMap); 31 | } 32 | -------------------------------------------------------------------------------- /plugins/deployment_manager/index.ts: -------------------------------------------------------------------------------- 1 | export { Deployed, DeploymentManager } from './DeploymentManager'; 2 | export { Migration, migration } from './Migration'; 3 | export { VerifyArgs } from './Verify'; 4 | export { debug } from './Utils'; -------------------------------------------------------------------------------- /plugins/deployment_manager/test/AliasesTest.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Cache } from '../Cache'; 3 | import { getAliases, getInvertedAliases, putAlias } from '../Aliases'; 4 | import { objectFromMap } from '../Utils'; 5 | import * as os from 'os'; 6 | 7 | describe('Aliases', () => { 8 | it('gets and sets aliases', async () => { 9 | let cache = new Cache('test-network', 'test-deployment', false, os.tmpdir()); 10 | 11 | expect(objectFromMap(await getAliases(cache))).to.eql({}); 12 | await putAlias(cache, 'poochie', '0x0000000000000000000000000000000000000000'); 13 | expect(objectFromMap(await getAliases(cache))).to.eql({ 14 | poochie: '0x0000000000000000000000000000000000000000', 15 | }); 16 | await putAlias(cache, 'poochie', '0x0000000000000000000000000000000000000001'); 17 | await putAlias(cache, 'itchy', '0x0000000000000000000000000000000000000001'); 18 | expect(objectFromMap(await getAliases(cache))).to.eql({ 19 | poochie: '0x0000000000000000000000000000000000000001', 20 | itchy: '0x0000000000000000000000000000000000000001', 21 | }); 22 | expect(objectFromMap(await getInvertedAliases(cache))).to.eql({ 23 | '0x0000000000000000000000000000000000000001': ['poochie', 'itchy'], 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/CacheTest.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { tempDir } from './TestHelpers'; 3 | 4 | import { Cache } from '../Cache'; 5 | import { objectFromMap } from '../Utils'; 6 | 7 | describe('Cache', () => { 8 | it('read and store values in-memory', async () => { 9 | let cache = new Cache('test-network', 'test-deployment', false, tempDir()); 10 | 11 | await cache.storeCache(['abc'], 5); 12 | 13 | expect(cache.cache).to.eql(new Map([['abc', 5]])); 14 | 15 | expect(await cache.readCache('abc')).to.eql(5); 16 | }); 17 | 18 | it('read and store values in-memory rel', async () => { 19 | let cache = new Cache('test-network', 'test-deployment', false, tempDir()); 20 | 21 | await cache.storeCache({ rel: 'abc' }, 5); 22 | 23 | expect(cache.cache).to.eql(new Map([['test-network', new Map([['test-deployment', new Map([['abc', 5]])]])]])); 24 | 25 | expect(await cache.readCache({ rel: 'abc' })).to.eql(5); 26 | }); 27 | 28 | it('read and store values to disk', async () => { 29 | let cache = new Cache('test-network', 'test-deployment', true, tempDir()); 30 | 31 | await cache.storeCache(['abc'], 5); 32 | 33 | expect(cache.cache).to.eql(new Map([['abc', 5]])); 34 | 35 | expect(await cache.readCache('abc')).to.eql(5); 36 | 37 | cache.cache = new Map(); // Kill in-memory key 38 | 39 | expect(await cache.readCache('abc')).to.eql(5); 40 | }); 41 | 42 | it('read and store values to disk rel', async () => { 43 | let cache = new Cache('test-network', 'test-deployment', true, tempDir()); 44 | 45 | await cache.storeCache({ rel: 'abc' }, 5); 46 | 47 | expect(cache.cache).to.eql(new Map([['test-network', new Map([['test-deployment', new Map([['abc', 5]])]])]])); 48 | 49 | expect(await cache.readCache({ rel: 'abc' })).to.eql(5); 50 | 51 | cache.cache = new Map(); // Kill in-memory key 52 | 53 | expect(await cache.readCache({ rel: 'abc' })).to.eql(5); 54 | }); 55 | 56 | describe('map', () => { 57 | it('read and store values in-memory rel', async () => { 58 | let cache = new Cache('test-network', 'test-deployment', false, tempDir()); 59 | 60 | await cache.storeMap({ rel: 'abc' }, new Map([['a', 5]])); 61 | 62 | expect(cache.cache).to.eql(new Map([['test-network', new Map([['test-deployment', new Map([['abc', new Map([['a', 5]])]])]])]])); 63 | 64 | expect(objectFromMap(await cache.readCache({ rel: 'abc' }))).to.eql({a: 5}); 65 | 66 | await cache.storeMap({ rel: 'abc' }, new Map([['a', 6]])); 67 | 68 | expect(objectFromMap(await cache.readCache({ rel: 'abc' }))).to.eql({a: 6}); 69 | }); 70 | }); 71 | 72 | describe('getFilePath', async () => { 73 | it('returns proper rel path', async () => { 74 | let dir = tempDir(); 75 | let cache = new Cache('test-network', 'test-deployment', true, dir); 76 | 77 | expect(cache.getFilePath({ rel: 'abc.cool' })).to.equal(`${dir}/test-network/test-deployment/abc.cool`); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/ContractMapTest.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import hre from 'hardhat'; 3 | 4 | import { tempDir } from './TestHelpers'; 5 | import { Cache } from '../Cache'; 6 | import { getBuildFile, storeBuildFile } from '../ContractMap'; 7 | import { deploy } from '../Deploy'; 8 | import { faucetTokenBuildFile, tokenArgs } from './DeployHelpers'; 9 | 10 | describe('ContractMap', () => { 11 | describe('storeBuildFile/getBuildFile', () => { 12 | it('gets what it stores', async () => { 13 | const cache = new Cache('test-network', 'test-deployment', false, tempDir()); 14 | const address = '0x0000000000000000000000000000000000000000'; 15 | await storeBuildFile(cache, 'test-network', address, faucetTokenBuildFile); 16 | const buildFile = await getBuildFile(cache, 'test-network', address); 17 | const noBuildFile = await getBuildFile(cache, 'no-test-network', address); 18 | expect(buildFile).to.eql(faucetTokenBuildFile); 19 | expect(noBuildFile).to.eql(undefined); 20 | }); 21 | 22 | it('stores deploys in the right place', async () => { 23 | const cache = new Cache('test-network', 'test-deployment', false, tempDir()); 24 | await deploy('test/FaucetToken.sol', tokenArgs, hre, { cache, network: 'test-network' }); 25 | const contracts = cache.cache.get('test-network').get('.contracts'); 26 | const noContracts = cache.cache.get('no-test-network'); 27 | expect(contracts.size).to.eql(1); 28 | expect(noContracts).to.eql(undefined); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/DeployTest.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { buildToken, deployBuild, faucetTokenBuildFile, tokenArgs, hre } from './DeployHelpers'; 3 | 4 | // TODO: Test verify 5 | // TODO: Test caching 6 | 7 | describe('Deploy', () => { 8 | it('deploy', async () => { 9 | let token = await buildToken(); 10 | expect(await token.symbol()).to.equal('TEST'); 11 | }); 12 | 13 | it('deployBuild', async () => { 14 | let token = await deployBuild(faucetTokenBuildFile, tokenArgs, hre, { network: 'test-network' }); 15 | expect(await token.symbol()).to.equal('TEST'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/MigrationTemplateTest.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | 4 | import { Cache } from '../Cache'; 5 | import { generateMigration, migrationTemplate } from '../MigrationTemplate'; 6 | 7 | import { tempDir } from './TestHelpers'; 8 | 9 | use(chaiAsPromised); 10 | 11 | export const expectedTemplate = `import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; 12 | import { migration } from '../../../../plugins/deployment_manager/Migration'; 13 | 14 | interface Vars {}; 15 | 16 | export default migration('1_cool', { 17 | prepare: async (deploymentManager: DeploymentManager) => { 18 | return {}; 19 | }, 20 | 21 | enact: async (governanceDeploymentManager: DeploymentManager, vars: Vars) => { 22 | // No governance changes 23 | } 24 | }); 25 | `; 26 | 27 | describe('MigrationTemplate', () => { 28 | it('test a simple template', async () => { 29 | expect(migrationTemplate({ timestamp: 1, name: 'cool' })).to.equal(expectedTemplate); 30 | }); 31 | 32 | it('should write to cache', async () => { 33 | let cache = new Cache('test-network', 'test-deployment', true, tempDir()); 34 | 35 | expect(await generateMigration(cache, 'cool', 1)).to.equal('1_cool.ts'); 36 | cache.clearMemory(); 37 | 38 | expect(await cache.readCache({ rel: ['migrations', '1_cool.ts'] }, (x) => x)).to.equal( 39 | expectedTemplate 40 | ); 41 | }); 42 | 43 | it('should fail if already exists', async () => { 44 | let cache = new Cache('test-network', 'test-deployment', true, tempDir()); 45 | 46 | expect(await generateMigration(cache, 'cool', 1)).to.equal('1_cool.ts'); 47 | 48 | // Try to re-store 49 | await expect(generateMigration(cache, 'cool', 1)).to.be.rejectedWith('Migration 1_cool.ts already exists.'); 50 | 51 | // Clear the cache 52 | cache.clearMemory(); 53 | 54 | // Try to re-store again 55 | await expect(generateMigration(cache, 'cool', 1)).to.be.rejectedWith('Migration 1_cool.ts already exists.'); 56 | 57 | expect(await cache.readCache({ rel: ['migrations', '1_cool.ts'] }, (x) => x)).to.equal( 58 | expectedTemplate 59 | ); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/MigrationTest.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat'; 2 | import { expect } from 'chai'; 3 | import { getArtifactSpec, loadMigrations, migration } from '../Migration'; 4 | import { DeploymentManager } from '../../deployment_manager/DeploymentManager'; 5 | 6 | describe('Migration', () => { 7 | it('test a simple migration', async () => { 8 | let x = []; 9 | let m = migration('test migration', { 10 | prepare: async (_deploymentManager) => { 11 | x = [...x, 'step 1']; 12 | return 'step 2'; 13 | }, 14 | enact: async (_deploymentManager, y) => { 15 | x = [...x, y]; 16 | } 17 | }); 18 | let dm = new DeploymentManager('test-network', 'test-deployment', hre); 19 | expect(m.name).to.eql('test migration'); 20 | expect(x).to.eql([]); 21 | let v = await m.actions.prepare(dm); 22 | expect(x).to.eql(['step 1']); 23 | await m.actions.enact(dm, v); 24 | expect(x).to.eql(['step 1', 'step 2']); 25 | }); 26 | 27 | it('loads a simple migration', async () => { 28 | let [m] = await loadMigrations([`${__dirname}/migration.ts`]); 29 | let dm = new DeploymentManager('test-network', 'test-market', hre); 30 | expect(m.name).to.eql('test migration'); 31 | expect(await m.actions.prepare(dm)).to.eql(['step 1']); 32 | expect(await m.actions.enact(dm, [])).to.eql(undefined); 33 | }); 34 | 35 | it('returns proper artifact file spec', async () => { 36 | let m = migration('test', { 37 | prepare: async () => null, 38 | enact: async () => { /* */ } 39 | }); 40 | 41 | expect(getArtifactSpec(m)).to.eql({ rel: ['artifacts', 'test.json'] }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/RootsTest.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Cache } from '../Cache'; 3 | import { getRoots, putRoots } from '../Roots'; 4 | import { objectFromMap } from '../Utils'; 5 | import { tempDir } from './TestHelpers'; 6 | 7 | describe('Roots', () => { 8 | it('gets and sets roots', async () => { 9 | let cache = new Cache('test-network', 'test-deployment', true, tempDir()); 10 | 11 | expect(objectFromMap(await getRoots(cache))).to.eql({}); 12 | await putRoots(cache, new Map([['poochie', '0x0000000000000000000000000000000000000000']])); 13 | expect(objectFromMap(await getRoots(cache))).to.eql({ 14 | poochie: '0x0000000000000000000000000000000000000000' 15 | }); 16 | 17 | cache.clearMemory(); 18 | 19 | expect(objectFromMap(await getRoots(cache))).to.eql({ 20 | poochie: '0x0000000000000000000000000000000000000000' 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/TestHelpers.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os'; 2 | import * as path from 'path'; 3 | 4 | // Just a fast, boring random algo to generate tmp directories 5 | function fakeRandom() { 6 | return Math.random().toString(36).substr(2, 5); 7 | } 8 | 9 | export function tempDir(): string { 10 | return path.join(os.tmpdir(), fakeRandom()); 11 | } 12 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/VerifyArgsTest.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat'; 2 | import { expect } from 'chai'; 3 | import { Cache } from '../Cache'; 4 | import { objectFromMap } from '../Utils'; 5 | import * as os from 'os'; 6 | import { deleteVerifyArgs, getVerifyArgs, putVerifyArgs } from '../VerifyArgs'; 7 | import { VerifyArgs } from '../Verify'; 8 | import { deploy, faucetTokenBuildFile } from './DeployHelpers'; 9 | 10 | describe('VerifyArgs', () => { 11 | it('gets, sets, and deletes verify args', async () => { 12 | const cache = new Cache('test-network', 'test-deployment', false, os.tmpdir()); 13 | 14 | const testContract = await deploy( 15 | 'test/Dog.sol', 16 | ['spot', '0x0000000000000000000000000000000000000001', []], 17 | hre, 18 | { cache, network: 'test-network' } 19 | ); 20 | const verifyArgs1: VerifyArgs = { via: 'artifacts', address: '0x0000000000000000000000000000000000000000', constructorArguments: [] }; 21 | const verifyArgs2: VerifyArgs = { via: 'buildfile', contract: testContract, buildFile: faucetTokenBuildFile, deployArgs: [] }; 22 | 23 | expect(objectFromMap(await getVerifyArgs(cache))).to.eql({}); 24 | await putVerifyArgs(cache, '0x0000000000000000000000000000000000000000', verifyArgs1); 25 | expect(objectFromMap(await getVerifyArgs(cache))).to.eql({ 26 | '0x0000000000000000000000000000000000000000': verifyArgs1, 27 | }); 28 | await putVerifyArgs(cache, '0x0000000000000000000000000000000000000000', verifyArgs2); 29 | await putVerifyArgs(cache, '0x0000000000000000000000000000000000000001', verifyArgs2); 30 | expect(objectFromMap(await getVerifyArgs(cache))).to.eql({ 31 | '0x0000000000000000000000000000000000000000': verifyArgs2, 32 | '0x0000000000000000000000000000000000000001': verifyArgs2, 33 | }); 34 | await deleteVerifyArgs(cache, '0x0000000000000000000000000000000000000000'); 35 | expect(objectFromMap(await getVerifyArgs(cache))).to.eql({ 36 | '0x0000000000000000000000000000000000000001': verifyArgs2, 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/VerifyTest.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat'; 2 | import nock from 'nock'; 3 | import { HardhatRuntimeEnvironment } from 'hardhat/types'; 4 | import * as fs from 'fs'; 5 | import * as path from 'path'; 6 | import { verifyContract } from '../Verify'; 7 | import { deployBuild } from '../Deploy'; 8 | import { buildToken, faucetTokenBuildFile, tokenArgs } from './DeployHelpers'; 9 | 10 | export function mockVerifySuccess(hre: HardhatRuntimeEnvironment) { 11 | let solcList = JSON.parse(fs.readFileSync(path.join(__dirname, './SolcList.json'), 'utf8')); 12 | 13 | // Note: we need to convince the prober task that this is goerli, which it's not. 14 | // So we'll fake the network name and the chain ID 15 | hre.config.etherscan.apiKey = { 16 | goerli: 'GOERLI_KEY', 17 | }; 18 | hre.network.name = 'goerli'; 19 | let sendOld = hre.network.provider.send.bind(hre.network.provider); 20 | hre.network.provider.send = function (...args) { 21 | if (args.length === 1 && args[0] === 'eth_chainId') { 22 | return Promise.resolve(5); 23 | } else { 24 | return sendOld(...args); 25 | } 26 | }; 27 | 28 | nock('https://solc-bin.ethereum.org/').get('/bin/list.json').reply(200, solcList); 29 | 30 | nock('https://api-goerli.etherscan.io/') 31 | .post('/api', /action=verifysourcecode/) 32 | .reply(200, { 33 | status: 1, 34 | message: 'OK', 35 | result: 'MYGUID', 36 | }); 37 | 38 | nock('https://api-goerli.etherscan.io/') 39 | .get('/api') 40 | .query({ 41 | apikey: 'GOERLI_KEY', 42 | module: 'contract', 43 | action: 'checkverifystatus', 44 | guid: 'MYGUID', 45 | }) 46 | .reply(200, { 47 | status: 1, 48 | message: 'OK', 49 | result: 'Pass - Verified', 50 | }); 51 | } 52 | 53 | describe('Verify', () => { 54 | beforeEach(async () => { 55 | nock.disableNetConnect(); 56 | }); 57 | 58 | describe('via artifacts', () => { 59 | it('verify from artifacts [success]', async () => { 60 | mockVerifySuccess(hre); 61 | let token = await buildToken(); 62 | await verifyContract( 63 | { via: 'artifacts', address: token.address, constructorArguments: tokenArgs }, 64 | hre, 65 | true 66 | ); 67 | }); 68 | }); 69 | 70 | describe('via buildfile', () => { 71 | it('verify from build file', async () => { 72 | mockVerifySuccess(hre); 73 | let contract = await deployBuild(faucetTokenBuildFile, tokenArgs, hre, { network: 'test-network' }); 74 | await verifyContract( 75 | { via: 'buildfile', contract, buildFile: faucetTokenBuildFile, deployArgs: tokenArgs }, 76 | hre, 77 | true 78 | ); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /plugins/deployment_manager/test/migration.ts: -------------------------------------------------------------------------------- 1 | import { migration } from '../Migration'; 2 | 3 | export default migration('test migration', { 4 | prepare: async (_deploymentManager) => { 5 | return ['step 1']; 6 | }, 7 | enact: async (_deploymentManager, _x) => { 8 | // no-op... 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /plugins/deployment_manager/type-extensions.ts: -------------------------------------------------------------------------------- 1 | import 'hardhat/types/config'; 2 | import { RelationConfigMap } from './RelationConfig'; 3 | 4 | export interface DeploymentManagerConfig { 5 | relationConfigMap?: RelationConfigMap; 6 | networks?: { 7 | [network: string]: { 8 | [deployment: string]: RelationConfigMap; 9 | }; 10 | }; 11 | } 12 | 13 | declare module 'hardhat/types/config' { 14 | interface HardhatUserConfig { 15 | deploymentManager?: DeploymentManagerConfig; 16 | } 17 | 18 | interface HardhatConfig { 19 | deploymentManager?: DeploymentManagerConfig; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /plugins/import/etherscan.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export interface Result { 4 | status: string; 5 | message: string; 6 | result: string; 7 | } 8 | 9 | export function getEtherscanApiUrl(network: string): string { 10 | let host = { 11 | kovan: 'api-kovan.etherscan.io', 12 | rinkeby: 'api-rinkeby.etherscan.io', 13 | ropsten: 'api-ropsten.etherscan.io', 14 | goerli: 'api-goerli.etherscan.io', 15 | mainnet: 'api.etherscan.io', 16 | fuji: 'api-testnet.snowtrace.io', 17 | avalanche: 'api.snowtrace.io', 18 | mumbai: 'api-mumbai.polygonscan.com', 19 | polygon: 'api.polygonscan.com' 20 | }[network]; 21 | 22 | if (!host) { 23 | throw new Error(`Unknown etherscan API host for network ${network}`); 24 | } 25 | 26 | return `https://${host}/api`; 27 | } 28 | 29 | export function getEtherscanUrl(network: string): string { 30 | let host = { 31 | kovan: 'kovan.etherscan.io', 32 | rinkeby: 'rinkeby.etherscan.io', 33 | ropsten: 'ropsten.etherscan.io', 34 | goerli: 'goerli.etherscan.io', 35 | mainnet: 'etherscan.io', 36 | fuji: 'testnet.snowtrace.io', 37 | avalanche: 'snowtrace.io', 38 | mumbai: 'mumbai.polygonscan.com', 39 | polygon: 'polygonscan.com', 40 | }[network]; 41 | 42 | if (!host) { 43 | throw new Error(`Unknown etherscan host for network ${network}`); 44 | } 45 | 46 | return `https://${host}`; 47 | } 48 | 49 | export function getEtherscanApiKey(network: string): string { 50 | let apiKey = { 51 | kovan: process.env.ETHERSCAN_KEY, 52 | rinkeby: process.env.ETHERSCAN_KEY, 53 | ropsten: process.env.ETHERSCAN_KEY, 54 | goerli: process.env.ETHERSCAN_KEY, 55 | mainnet: process.env.ETHERSCAN_KEY, 56 | fuji: process.env.SNOWTRACE_KEY, 57 | avalanche: process.env.SNOWTRACE_KEY, 58 | mumbai: process.env.POLYGONSCAN_KEY, 59 | polygon: process.env.POLYGONSCAN_KEY, 60 | }[network]; 61 | 62 | if (!apiKey) { 63 | throw new Error(`Unknown etherscan API key for network ${network}`); 64 | } 65 | 66 | return apiKey; 67 | } 68 | 69 | export async function get(url, data) { 70 | const res = (await axios.get(url, { params: data }))['data']; 71 | return res; 72 | } 73 | -------------------------------------------------------------------------------- /plugins/scenario/Loader.ts: -------------------------------------------------------------------------------- 1 | import fg from 'fast-glob'; 2 | import * as path from 'path'; 3 | import { Scenario, ScenarioFlags, Property, Initializer, Forker, Constraint, Transformer } from './Scenario'; 4 | 5 | class Loader { 6 | scenarios: { [name: string]: Scenario }; 7 | 8 | constructor() { 9 | this.scenarios = {}; 10 | } 11 | 12 | addScenario( 13 | name: string, 14 | requirements: R, 15 | property: Property, 16 | initializer: Initializer, 17 | transformer: Transformer, 18 | forker: Forker, 19 | constraints: Constraint[], 20 | flags: ScenarioFlags = null 21 | ) { 22 | if (this.scenarios[name]) { 23 | throw new Error(`Duplicate scenarios by name: ${name}`); 24 | } 25 | this.scenarios[name] = new Scenario( 26 | name, 27 | requirements, 28 | property, 29 | initializer, 30 | transformer, 31 | forker, 32 | constraints, 33 | flags 34 | ); 35 | } 36 | 37 | getScenarios(): { [name: string]: Scenario } { 38 | return this.scenarios; 39 | } 40 | } 41 | 42 | let loader: any; 43 | 44 | function setupLoader() { 45 | if (loader) { 46 | throw new Error('Loader already initialized'); 47 | } 48 | 49 | loader = new Loader(); 50 | } 51 | 52 | export function getLoader(): Loader { 53 | if (!loader) { 54 | throw new Error('Loader not initialized'); 55 | } 56 | 57 | return >loader; 58 | } 59 | 60 | export async function loadScenarios(glob: string): Promise<{ [name: string]: Scenario }> { 61 | setupLoader(); 62 | 63 | const entries = await fg(glob); // Grab all potential scenario files 64 | 65 | for (let entry of entries) { 66 | let entryPath = path.join(process.cwd(), entry); 67 | 68 | /* Import scenario file */ 69 | await import(entryPath); 70 | /* Import complete */ 71 | } 72 | 73 | return loader.getScenarios(); 74 | } 75 | -------------------------------------------------------------------------------- /plugins/scenario/Scenario.ts: -------------------------------------------------------------------------------- 1 | import { World } from './World'; 2 | import { getStack } from './Stack'; 3 | 4 | // A solution modifies a given context and world in a way that satisfies a constraint. 5 | export type Solution = (T, World) => Promise; 6 | 7 | // A constraint is capable of producing solutions for a context and world *like* the ones provided. 8 | // A constraint can also check a given context and world to see if they *actually* satisfy it. 9 | // Note: `solve` and `check` are expected to treat the context and world as immutable. 10 | export interface Constraint { 11 | solve( 12 | requirements: R, 13 | context: T, 14 | world: World 15 | ): Promise | Solution[] | null>; 16 | check(requirements: R, context: T, world: World): Promise; 17 | } 18 | 19 | export type Receipt = { cumulativeGasUsed: { toNumber: () => number } }; 20 | export type Property = (properties: U, context: T, world: World) => Promise; 21 | export type Initializer = (world: World) => Promise; 22 | export type Transformer = (context: T) => Promise; 23 | export type Forker = (T, world: World) => Promise; 24 | 25 | export type ScenarioFlags = null | 'only' | 'skip'; 26 | 27 | export class Scenario { 28 | name: string; 29 | file: string | null; 30 | requirements: R; 31 | property: Property; 32 | initializer: Initializer; 33 | transformer: Transformer; 34 | forker: Forker; 35 | constraints: Constraint[]; 36 | flags: ScenarioFlags; 37 | 38 | constructor(name, requirements, property, initializer, transformer, forker, constraints, flags) { 39 | this.name = name; 40 | this.requirements = requirements; 41 | this.property = property; 42 | this.initializer = initializer; 43 | this.transformer = transformer; 44 | this.forker = forker; 45 | this.constraints = constraints; 46 | this.flags = flags; 47 | let frame = getStack(3); 48 | this.file = frame[0] ? frame[0].file : null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /plugins/scenario/Stack.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface StackCall { 3 | function?: string; 4 | file?: string; 5 | line?: number; 6 | char?: number; 7 | } 8 | 9 | export function getStack(skipFrames: number = 1): StackCall[] { 10 | let regex = /at (?[^ ]+) [(](?[^ ]+?)(:(?\d+))?(:(?\d+))?[)]/g; 11 | let stack = new Error().stack; 12 | let next; 13 | let trace = []; 14 | let index = 0; 15 | 16 | while (null != (next=regex.exec(stack))) { 17 | if (++index > skipFrames) { 18 | trace.push({ 19 | function: next.groups['function'], 20 | file: next.groups['file'], 21 | line: next.groups['line'] ? Number(next.groups['line']) : undefined, 22 | char: next.groups['char'] ? Number(next.groups['char']) : undefined, 23 | }); 24 | } 25 | } 26 | 27 | return trace; 28 | } 29 | -------------------------------------------------------------------------------- /plugins/scenario/index.ts: -------------------------------------------------------------------------------- 1 | import { Constraint, Forker, Initializer, Property, ScenarioFlags, Transformer } from './Scenario'; 2 | import { getLoader } from './Loader'; 3 | export { Constraint, Initializer, Property, Scenario, Solution, Transformer } from './Scenario'; 4 | export { ForkSpec, World } from './World'; 5 | export { debug } from '../deployment_manager/Utils'; 6 | 7 | export interface ScenarioBuilder { 8 | (name: string, requirements: R, property: Property): void; 9 | only: (name: string, requirements: R, property: Property) => void; 10 | skip: (name: string, requirements: R, property: Property) => void; 11 | } 12 | 13 | export function addScenario( 14 | name: string, 15 | requirements: R, 16 | property: Property, 17 | initializer: Initializer, 18 | transformer: Transformer, 19 | forker: Forker, 20 | constraints: Constraint[], 21 | flags: ScenarioFlags = null 22 | ) { 23 | getLoader().addScenario( 24 | name, 25 | requirements, 26 | property, 27 | initializer, 28 | transformer, 29 | forker, 30 | constraints, 31 | flags 32 | ); 33 | } 34 | 35 | export function buildScenarioFn( 36 | initializer: Initializer, 37 | transformer: Transformer, 38 | forker: Forker, 39 | constraints: Constraint[] 40 | ) { 41 | const addScenarioWithOpts = 42 | (flags: ScenarioFlags) => (name: string, requirements: R, property: Property) => { 43 | addScenario( 44 | name, 45 | requirements, 46 | property, 47 | initializer, 48 | transformer, 49 | forker, 50 | constraints, 51 | flags 52 | ); 53 | }; 54 | 55 | const res: ScenarioBuilder = Object.assign(addScenarioWithOpts(null), { 56 | only: addScenarioWithOpts('only'), 57 | skip: addScenarioWithOpts('skip'), 58 | }); 59 | 60 | return res; 61 | } 62 | -------------------------------------------------------------------------------- /plugins/scenario/type-extensions.ts: -------------------------------------------------------------------------------- 1 | import 'hardhat/types/config'; 2 | import { ScenarioConfig } from './types'; 3 | 4 | declare module 'hardhat/types/config' { 5 | interface HardhatUserConfig { 6 | // optional? 7 | scenario: ScenarioConfig; 8 | } 9 | 10 | interface HardhatConfig { 11 | scenario: ScenarioConfig; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugins/scenario/types.ts: -------------------------------------------------------------------------------- 1 | import { ForkSpec } from './World'; 2 | 3 | export interface ScenarioConfig { 4 | bases: ForkSpec[]; 5 | } 6 | -------------------------------------------------------------------------------- /plugins/scenario/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; 2 | import { DeploymentManager } from '../../../plugins/deployment_manager'; 3 | 4 | export async function impersonateAddress(dm: DeploymentManager, address: string): Promise { 5 | await dm.hre.network.provider.request({ 6 | method: 'hardhat_impersonateAccount', 7 | params: [address], 8 | }); 9 | return await dm.getSigner(address); 10 | } 11 | -------------------------------------------------------------------------------- /plugins/scenario/worker/BootstrapWorker.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { workerData } = require('worker_threads'); 3 | 4 | // Note: this is bootstrap code to run `worker.ts` from a worker thread that only knows nodejs at birth 5 | require('ts-node').register(); 6 | let { run } = require(path.resolve(__dirname, './Worker.ts')); 7 | 8 | run(workerData).catch((e) => { 9 | console.error(e); 10 | setTimeout(() => { // Deferral to allow potential console flush 11 | throw e; 12 | }, 0); 13 | }); 14 | -------------------------------------------------------------------------------- /plugins/scenario/worker/Config.ts: -------------------------------------------------------------------------------- 1 | // TODO: This doesn't belong here 2 | export const scenarioGlob = 'scenario/**.ts'; 3 | export const workerCount = 3; 4 | export const defaultFormats = { 5 | console: {}, 6 | json: { 7 | output: 'scenario-results.json' 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /plugins/scenario/worker/HardhatContext.ts: -------------------------------------------------------------------------------- 1 | import { HardhatContext } from 'hardhat/internal/context'; 2 | export { HardhatContext } from 'hardhat/internal/context'; 3 | import { HardhatConfig } from 'hardhat/types/config'; 4 | export { HardhatConfig } from 'hardhat/types/config'; 5 | import { HardhatArguments } from 'hardhat/types/runtime'; 6 | export { HardhatArguments } from 'hardhat/types/runtime'; 7 | import { Environment } from 'hardhat/internal/core/runtime-environment'; 8 | import { getEnvHardhatArguments } from 'hardhat/internal/core/params/env-variables'; 9 | import { HARDHAT_PARAM_DEFINITIONS } from 'hardhat/internal/core/params/hardhat-params'; 10 | 11 | export type GlobalWithHardhatContext = typeof global & { 12 | __hardhatContext: HardhatContext; 13 | }; 14 | 15 | export function createContext(config: HardhatConfig, hardhatArguments: HardhatArguments) { 16 | // TODO: I'm not sure this is ideal, inspired by these lines: 17 | // https://github.com/nomiclabs/hardhat/blob/4f108b51fc7f87bcf7f173a4301b5973918b4903/packages/hardhat-core/src/internal/context.ts#L13-L40 18 | let ctx = HardhatContext.createHardhatContext(); 19 | 20 | let env = new Environment( 21 | config, 22 | hardhatArguments, 23 | ctx.tasksDSL.getTaskDefinitions(), 24 | ctx.extendersManager.getExtenders(), 25 | ctx.experimentalHardhatNetworkMessageTraceHooks 26 | ); 27 | 28 | ctx.setHardhatRuntimeEnvironment(env); 29 | 30 | return ((global as GlobalWithHardhatContext).__hardhatContext = ctx); 31 | } 32 | 33 | export function getContext(): HardhatContext { 34 | return (global as GlobalWithHardhatContext).__hardhatContext; 35 | } 36 | 37 | export function getConfig(): HardhatConfig { 38 | return getContext().environment.config; 39 | } 40 | 41 | export function setConfig(config: HardhatConfig) { 42 | (getContext().environment).config = config; 43 | } 44 | 45 | export function getHardhatArguments(): HardhatArguments { 46 | return getEnvHardhatArguments(HARDHAT_PARAM_DEFINITIONS, process.env); 47 | } 48 | -------------------------------------------------------------------------------- /plugins/scenario/worker/SimpleWorker.ts: -------------------------------------------------------------------------------- 1 | import { run, WorkerData } from './Worker'; 2 | 3 | export type Handler = (any) => Promise; 4 | 5 | export class SimpleWorker { 6 | workerData: WorkerData; 7 | childMessages: any[]; 8 | parentMessages: any[]; 9 | childHandlers: Handler[]; 10 | parentHandlers: Handler[]; 11 | 12 | constructor(workerData: WorkerData) { 13 | this.workerData = workerData; 14 | this.childMessages = []; 15 | this.parentMessages = []; 16 | this.childHandlers = []; 17 | this.parentHandlers = []; 18 | } 19 | 20 | // Register to child messages 21 | on(msg: 'message', f: (message: any) => Promise) { 22 | this.parentHandlers.push(f); 23 | 24 | this.parentMessages.forEach((msg) => f(msg)); 25 | this.parentMessages = []; // Clear out messages 26 | } 27 | 28 | // Post message to child 29 | postMessage(message: any) { 30 | if (this.childHandlers.length > 0) { 31 | this.childHandlers.forEach((f) => f(message)); 32 | } else { 33 | this.childMessages.push(message); // store if no handlers 34 | } 35 | } 36 | 37 | // Register to parent messages 38 | onParent(msg: 'message', f: (message: any) => Promise) { 39 | this.childHandlers.push(f); 40 | 41 | this.childMessages.forEach((msg) => f(msg)); 42 | this.childMessages = []; // Clear out messages 43 | } 44 | 45 | // Post message to parent 46 | postParentMessage(message: any) { 47 | this.parentHandlers.forEach((f) => f(message)); 48 | } 49 | 50 | async run() { 51 | try { 52 | await run({ ...this.workerData, worker: this }); 53 | } catch (e) { 54 | console.error(e); 55 | setTimeout(() => { // Deferral to allow potential console flush 56 | throw e; 57 | }, 0); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /scenario/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multijump/comet/8ac17412ff4a144eca1c35846d416bf4cd28a32f/scenario/.gitkeep -------------------------------------------------------------------------------- /scenario/AllowScenario.ts: -------------------------------------------------------------------------------- 1 | import { scenario } from './context/CometContext'; 2 | import { expect } from 'chai'; 3 | 4 | scenario('Comet#allow > allows a user to authorize a manager', {}, async ({ comet, actors }) => { 5 | const { albert, betty } = actors; 6 | 7 | const txn = await albert.allow(betty, true); 8 | 9 | expect(await comet.isAllowed(albert.address, betty.address)).to.be.true; 10 | 11 | return txn; // return txn to measure gas 12 | }); 13 | 14 | scenario('Comet#allow > allows a user to rescind authorization', {}, async ({ comet, actors }) => { 15 | const { albert, betty } = actors; 16 | 17 | await albert.allow(betty, true); 18 | 19 | expect(await comet.isAllowed(albert.address, betty.address)).to.be.true; 20 | 21 | await albert.allow(betty, false); 22 | 23 | expect(await comet.isAllowed(albert.address, betty.address)).to.be.false; 24 | }); 25 | -------------------------------------------------------------------------------- /scenario/ApproveThisScenario.ts: -------------------------------------------------------------------------------- 1 | import { scenario } from './context/CometContext'; 2 | import { expectRevertCustom } from './utils'; 3 | import { expect } from 'chai'; 4 | import { constants } from 'ethers'; 5 | 6 | scenario('Comet#approveThis > allows governor to authorize and rescind authorization for Comet ERC20', {}, async ({ comet, timelock, actors }, context) => { 7 | const { admin } = actors; 8 | 9 | await context.setNextBaseFeeToZero(); 10 | await admin.approveThis(timelock.address, comet.address, constants.MaxUint256, { gasPrice: 0 }); 11 | 12 | expect(await comet.isAllowed(comet.address, timelock.address)).to.be.true; 13 | 14 | await context.setNextBaseFeeToZero(); 15 | await admin.approveThis(timelock.address, comet.address, 0, { gasPrice: 0 }); 16 | 17 | expect(await comet.isAllowed(comet.address, timelock.address)).to.be.false; 18 | }); 19 | 20 | scenario('Comet#approveThis > allows governor to authorize and rescind authorization for non-Comet ERC20', {}, async ({ comet, timelock, actors }, context) => { 21 | const { admin } = actors; 22 | const baseTokenAddress = await comet.baseToken(); 23 | const baseToken = context.getAssetByAddress(baseTokenAddress); 24 | 25 | const newAllowance = 999_888n; 26 | await context.setNextBaseFeeToZero(); 27 | await admin.approveThis(timelock.address, baseTokenAddress, newAllowance, { gasPrice: 0 }); 28 | 29 | expect(await baseToken.allowance(comet.address, timelock.address)).to.be.equal(newAllowance); 30 | 31 | await context.setNextBaseFeeToZero(); 32 | await admin.approveThis(timelock.address, baseTokenAddress, 0, { gasPrice: 0 }); 33 | 34 | expect(await baseToken.allowance(comet.address, timelock.address)).to.be.equal(0n); 35 | }); 36 | 37 | scenario('Comet#approveThis > reverts if not called by governor', {}, async ({ comet, timelock }) => { 38 | await expectRevertCustom(comet.approveThis(timelock.address, comet.address, constants.MaxUint256), 'Unauthorized()'); 39 | }); 40 | -------------------------------------------------------------------------------- /scenario/CometScenario.ts: -------------------------------------------------------------------------------- 1 | import { scenario } from './context/CometContext'; 2 | import { expect } from 'chai'; 3 | 4 | scenario('initializes governor correctly', {}, async ({ comet, timelock }) => { 5 | // TODO: Make this more interesting. 6 | expect(await comet.governor()).to.equal(timelock.address); 7 | }); 8 | 9 | scenario('has assets', {}, async ({ comet, assets }) => { 10 | let baseToken = await comet.baseToken(); 11 | let numAssets = await comet.numAssets(); 12 | let collateralAssets = await Promise.all(Array(numAssets).fill(0).map((_, i) => comet.getAssetInfo(i))); 13 | let contextAssets = 14 | Object.values(assets) 15 | .map((asset) => asset.address) // grab asset address 16 | .filter((address) => address.toLowerCase() !== baseToken.toLowerCase()); // filter out base token 17 | expect(collateralAssets.map(a => a.asset)).to.have.members(contextAssets); 18 | }); 19 | 20 | scenario('requires upgrade', {}, async () => { 21 | // Nothing currently here 22 | }); 23 | -------------------------------------------------------------------------------- /scenario/PauseGuardianScenario.ts: -------------------------------------------------------------------------------- 1 | import { scenario } from './context/CometContext'; 2 | import { expectRevertCustom } from './utils'; 3 | import { expect } from 'chai'; 4 | 5 | scenario( 6 | 'Comet#pause > governor pauses market actions', 7 | { 8 | pause: { 9 | all: false, 10 | }, 11 | }, 12 | async ({ comet, actors }, context) => { 13 | const { admin } = actors; 14 | 15 | expect(await comet.isSupplyPaused()).to.be.false; 16 | expect(await comet.isTransferPaused()).to.be.false; 17 | expect(await comet.isWithdrawPaused()).to.be.false; 18 | expect(await comet.isAbsorbPaused()).to.be.false; 19 | expect(await comet.isBuyPaused()).to.be.false; 20 | 21 | await context.setNextBaseFeeToZero(); 22 | const txn = await admin.pause({ 23 | supplyPaused: true, 24 | transferPaused: true, 25 | withdrawPaused: true, 26 | absorbPaused: true, 27 | buyPaused: true, 28 | }, { gasPrice: 0 }); 29 | 30 | expect(await comet.isSupplyPaused()).to.be.true; 31 | expect(await comet.isTransferPaused()).to.be.true; 32 | expect(await comet.isWithdrawPaused()).to.be.true; 33 | expect(await comet.isAbsorbPaused()).to.be.true; 34 | expect(await comet.isBuyPaused()).to.be.true; 35 | 36 | return txn; // return txn to measure gas 37 | } 38 | ); 39 | 40 | scenario( 41 | 'Comet#pause > pause guardian pauses market actions', 42 | { 43 | pause: { 44 | all: false, 45 | }, 46 | }, 47 | async ({ comet, actors }, context) => { 48 | const { pauseGuardian } = actors; 49 | 50 | expect(await comet.isSupplyPaused()).to.be.false; 51 | expect(await comet.isTransferPaused()).to.be.false; 52 | expect(await comet.isWithdrawPaused()).to.be.false; 53 | expect(await comet.isAbsorbPaused()).to.be.false; 54 | expect(await comet.isBuyPaused()).to.be.false; 55 | 56 | await context.setNextBaseFeeToZero(); 57 | await pauseGuardian.pause({ 58 | supplyPaused: true, 59 | transferPaused: true, 60 | withdrawPaused: true, 61 | absorbPaused: true, 62 | buyPaused: true, 63 | }, { gasPrice: 0 }); 64 | 65 | expect(await comet.isSupplyPaused()).to.be.true; 66 | expect(await comet.isTransferPaused()).to.be.true; 67 | expect(await comet.isWithdrawPaused()).to.be.true; 68 | expect(await comet.isAbsorbPaused()).to.be.true; 69 | expect(await comet.isBuyPaused()).to.be.true; 70 | } 71 | ); 72 | 73 | scenario( 74 | 'CometRevertCustom#pause > reverts if not called by governor or pause guardian', 75 | { 76 | pause: { 77 | all: false, 78 | }, 79 | }, 80 | async ({ actors }) => { 81 | const { albert } = actors; 82 | await expectRevertCustom( 83 | albert.pause({ 84 | supplyPaused: true, 85 | transferPaused: true, 86 | withdrawPaused: true, 87 | absorbPaused: true, 88 | buyPaused: true, 89 | }), 90 | 'Unauthorized()' 91 | ); 92 | } 93 | ); 94 | -------------------------------------------------------------------------------- /scenario/WithdrawReservesScenario.ts: -------------------------------------------------------------------------------- 1 | import { scenario } from './context/CometContext'; 2 | import { expectRevertCustom } from './utils'; 3 | import { expect } from 'chai'; 4 | 5 | // XXX we could use a Comet reserves constraint here 6 | scenario( 7 | 'Comet#withdrawReserves > governor withdraw reserves', 8 | { 9 | tokenBalances: { 10 | betty: { $base: '== 100000' }, 11 | albert: { $base: '== 0' }, 12 | }, 13 | }, 14 | async ({ comet, timelock, actors }, context) => { 15 | const { admin, albert, betty } = actors; 16 | 17 | const baseToken = context.getAssetByAddress(await comet.baseToken()); 18 | const scale = (await comet.baseScale()).toBigInt(); 19 | 20 | // Since we don't have a constraint to set Comet reserves, we'll be transferring 100K base tokens to Comet from an actor 21 | // XXX however, this wouldn't work if reserves on testnet are too negative 22 | await betty.transferErc20(baseToken.address, comet.address, 100000n * scale); 23 | const cometBaseBalance = await baseToken.balanceOf(comet.address); 24 | 25 | expect(await comet.governor()).to.equal(timelock.address); 26 | 27 | const toWithdrawAmount = 10n * scale; 28 | await context.setNextBaseFeeToZero(); 29 | const txn = await admin.withdrawReserves(albert.address, toWithdrawAmount, { gasPrice: 0 }); 30 | 31 | expect(await baseToken.balanceOf(comet.address)).to.equal(cometBaseBalance - toWithdrawAmount); 32 | expect(await baseToken.balanceOf(albert.address)).to.equal(toWithdrawAmount); 33 | 34 | return txn; // return txn to measure gas 35 | } 36 | ); 37 | 38 | scenario( 39 | 'Comet#withdrawReserves > reverts if not called by governor', 40 | { 41 | tokenBalances: { 42 | $comet: { $base: 100 }, 43 | }, 44 | }, 45 | async ({ actors }) => { 46 | const { albert } = actors; 47 | await expectRevertCustom(albert.withdrawReserves(albert.address, 10), 'Unauthorized()'); 48 | } 49 | ); 50 | 51 | 52 | scenario( 53 | 'Comet#withdrawReserves > reverts if not enough reserves are owned by protocol', 54 | { 55 | tokenBalances: { 56 | $comet: { $base: '== 100' }, 57 | }, 58 | }, 59 | async ({ comet, actors }, context) => { 60 | const { admin, albert } = actors; 61 | 62 | const scale = (await comet.baseScale()).toBigInt(); 63 | 64 | await context.setNextBaseFeeToZero(); 65 | await expectRevertCustom( 66 | admin.withdrawReserves(albert.address, 101n * scale, { gasPrice: 0 }), 67 | 'InsufficientReserves()' 68 | ); 69 | } 70 | ); 71 | 72 | // XXX add scenario that tests for a revert when reserves are reduced by 73 | // totalSupplyBase 74 | -------------------------------------------------------------------------------- /scenario/constraints/FilterConstraint.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Constraint, World } from '../../plugins/scenario'; 3 | import { CometContext } from '../context/CometContext'; 4 | import { Requirements } from './Requirements'; 5 | 6 | export class FilterConstraint implements Constraint { 7 | async solve(requirements: R, context: T) { 8 | const filterFn = requirements.filter; 9 | if (!filterFn) { 10 | return null; 11 | } 12 | 13 | if (await filterFn(context)) { 14 | return null; 15 | } else { 16 | return []; // filter out this solution 17 | } 18 | } 19 | 20 | async check(requirements: R, context: T, _world: World) { 21 | const filterFn = requirements.filter; 22 | if (!filterFn) { 23 | return; 24 | } 25 | 26 | expect(await filterFn(context)).to.be.equals(true); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scenario/constraints/ModernConstraint.ts: -------------------------------------------------------------------------------- 1 | import { Constraint } from '../../plugins/scenario'; 2 | import { CometContext } from '../context/CometContext'; 3 | import { ProtocolConfiguration } from '../../src/deploy'; 4 | import { getFuzzedRequirements } from './Fuzzing'; 5 | import { Requirements } from './Requirements'; 6 | 7 | interface ModernConfig { 8 | // Whether to upgrade or Comet config overrides to use for an upgrade 9 | upgrade: ProtocolConfiguration; 10 | } 11 | 12 | async function getModernConfigs(context: CometContext, requirements: Requirements): Promise { 13 | const currentConfig = await context.getConfiguration(); 14 | const fuzzedConfigs = getFuzzedRequirements(requirements).map((r) => ({ 15 | upgrade: r.upgrade && Object.assign({}, currentConfig, r.upgrade), 16 | })); 17 | return fuzzedConfigs; 18 | } 19 | 20 | export class ModernConstraint implements Constraint { 21 | async solve(requirements: R, context: T) { 22 | const configs = await getModernConfigs(context, requirements); 23 | const solutions = []; 24 | for (const config of configs) { 25 | if (config.upgrade) { 26 | solutions.push(async function solution(ctx: T): Promise { 27 | return await ctx.upgrade(config.upgrade) as T; // It's been modified 28 | }); 29 | } 30 | } 31 | return solutions.length > 0 ? solutions : null; 32 | } 33 | 34 | async check(_requirements: R, _context: T) { 35 | return; // XXX 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scenario/constraints/Requirements.ts: -------------------------------------------------------------------------------- 1 | // TODO: Could define strict types for these objects 2 | export interface Requirements { 3 | filter?: (context) => Promise; 4 | tokenBalances?: object; // Token balance constraint 5 | cometBalances?: object; // Comet balance constraint 6 | upgrade?: boolean | object; // Modern constraint 7 | pause?: object; // Pause constraint 8 | utilization?: number; // Utilization constraint 9 | } -------------------------------------------------------------------------------- /scenario/constraints/index.ts: -------------------------------------------------------------------------------- 1 | export { TokenBalanceConstraint } from './TokenBalanceConstraint'; 2 | export { PauseConstraint } from './PauseConstraint'; 3 | export { ModernConstraint } from './ModernConstraint'; 4 | export { UtilizationConstraint } from './UtilizationConstraint'; 5 | export { CometBalanceConstraint } from './CometBalanceConstraint'; 6 | export { MigrationConstraint, VerifyMigrationConstraint } from './MigrationConstraint'; 7 | export { ProposalConstraint } from './ProposalConstraint'; 8 | export { FilterConstraint } from './FilterConstraint'; 9 | -------------------------------------------------------------------------------- /scenario/context/Address.ts: -------------------------------------------------------------------------------- 1 | 2 | export function getAddressFromNumber(n: number): string { 3 | // If you think this is weird and hacky to get an address, you're right. 4 | let zeroAddress = '0000000000000000000000000000000000000000'; 5 | let numberHex = n.toString(16); 6 | let address = `${zeroAddress}${numberHex}`.slice(-40); 7 | 8 | return '0x' + address; 9 | } 10 | 11 | export type AddressLike = string | { address: string }; 12 | 13 | export function resolveAddress(v: AddressLike): string { 14 | return typeof(v) === 'string' ? v : v.address; 15 | } 16 | -------------------------------------------------------------------------------- /scenario/context/CometAsset.ts: -------------------------------------------------------------------------------- 1 | import { Overrides } from 'ethers'; 2 | import { ERC20 } from '../../build/types'; 3 | import CometActor from './CometActor'; 4 | import { AddressLike, resolveAddress } from './Address'; 5 | import { constants } from 'ethers'; 6 | import { wait } from '../../test/helpers'; 7 | 8 | export default class CometAsset { 9 | token: ERC20; 10 | address: string; 11 | 12 | constructor(token: ERC20) { 13 | this.token = token; 14 | this.address = token.address; 15 | } 16 | 17 | static fork(asset: CometAsset): CometAsset { 18 | return new CometAsset(asset.token); 19 | } 20 | 21 | async balanceOf(actorOrAddress: string | CometActor): Promise { 22 | let address: string; 23 | if (typeof(actorOrAddress) === 'string') { 24 | address = actorOrAddress; 25 | } else { 26 | address = actorOrAddress.address; 27 | } 28 | 29 | return (await this.token.balanceOf(address)).toBigInt(); 30 | } 31 | 32 | async transfer(from: CometActor, amount: number | bigint, recipient: CometAsset | string, overrides: Overrides = {}) { 33 | let recipientAddress = typeof(recipient) === 'string' ? recipient : recipient.address; 34 | 35 | await wait(this.token.connect(from.signer).transfer(recipientAddress, amount, overrides)); 36 | } 37 | 38 | async approve(from: CometActor, spender: AddressLike, amount?: number | bigint) { 39 | let spenderAddress = resolveAddress(spender); 40 | let finalAmount = amount ?? constants.MaxUint256; 41 | await wait(this.token.connect(from.signer).approve(spenderAddress, finalAmount)); 42 | } 43 | 44 | async allowance(owner: AddressLike, spender: AddressLike): Promise { 45 | let ownerAddress = resolveAddress(owner); 46 | let spenderAddress = resolveAddress(spender); 47 | return (await this.token.allowance(ownerAddress, spenderAddress)).toBigInt(); 48 | } 49 | 50 | async decimals(): Promise { 51 | return this.token.decimals(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scenario/context/Gov.ts: -------------------------------------------------------------------------------- 1 | export { IGovernorBravo } from '../../build/types'; 2 | 3 | export enum ProposalState { 4 | Pending, 5 | Active, 6 | Canceled, 7 | Defeated, 8 | Succeeded, 9 | Queued, 10 | Expired, 11 | Executed 12 | } 13 | 14 | export type OpenProposal = { id: number, startBlock: number, endBlock: number }; 15 | -------------------------------------------------------------------------------- /scenario/utils/hreUtils.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentManager } from '../../plugins/deployment_manager'; 2 | 3 | export async function setNextBaseFeeToZero(dm: DeploymentManager) { 4 | await dm.hre.network.provider.send('hardhat_setNextBlockBaseFeePerGas', ['0x0']); 5 | } 6 | 7 | export async function mineBlocks(dm: DeploymentManager, blocks: number) { 8 | await dm.hre.network.provider.send('hardhat_mine', [`0x${blocks.toString(16)}`]); 9 | } 10 | 11 | export async function setNextBlockTimestamp(dm: DeploymentManager, timestamp: number) { 12 | await dm.hre.ethers.provider.send('evm_setNextBlockTimestamp', [timestamp]); 13 | } -------------------------------------------------------------------------------- /scenario/utils/relayMessage.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentManager } from '../../plugins/deployment_manager'; 2 | import relayMumbaiMessage from './relayMumbaiMessage'; 3 | 4 | export default async function relayMessage( 5 | governanceDeploymentManager: DeploymentManager, 6 | bridgeDeploymentManager: DeploymentManager 7 | ) { 8 | const bridgeNetwork = bridgeDeploymentManager.network; 9 | switch (bridgeNetwork) { 10 | case 'mumbai': 11 | await relayMumbaiMessage(governanceDeploymentManager, bridgeDeploymentManager); 12 | break; 13 | default: 14 | throw new Error(`No message relay implementation from ${bridgeNetwork} -> ${governanceDeploymentManager.network}`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scenario/utils/relayMumbaiMessage.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentManager } from '../../plugins/deployment_manager'; 2 | import { impersonateAddress } from '../../plugins/scenario/utils'; 3 | import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; 4 | import { Event } from 'ethers'; 5 | 6 | export default async function relayMumbaiMessage( 7 | governanceDeploymentManager: DeploymentManager, 8 | bridgeDeploymentManager: DeploymentManager, 9 | ) { 10 | const MUMBAI_RECEIVER_ADDRESSS = '0x0000000000000000000000000000000000001001'; 11 | const EVENT_LISTENER_TIMEOUT = 60000; 12 | 13 | const stateSender = await governanceDeploymentManager.getContractOrThrow('stateSender'); 14 | const bridgeReceiver = await bridgeDeploymentManager.getContractOrThrow('bridgeReceiver'); 15 | const fxChild = await bridgeDeploymentManager.getContractOrThrow('fxChild'); 16 | 17 | // listen on events on the fxRoot contract 18 | const stateSyncedListenerPromise = new Promise((resolve, reject) => { 19 | const filter = stateSender.filters.StateSynced(); 20 | 21 | governanceDeploymentManager.hre.ethers.provider.on(filter, (log) => { 22 | resolve(log); 23 | }); 24 | 25 | setTimeout(() => { 26 | reject(new Error('StateSender.StateSynced event listener timed out')); 27 | }, EVENT_LISTENER_TIMEOUT); 28 | }); 29 | 30 | const stateSyncedEvent = await stateSyncedListenerPromise as Event; 31 | const { args: { data: stateSyncedData } } = stateSender.interface.parseLog(stateSyncedEvent); 32 | 33 | const mumbaiReceiverSigner = await impersonateAddress(bridgeDeploymentManager, MUMBAI_RECEIVER_ADDRESSS); 34 | 35 | await setNextBaseFeeToZero(bridgeDeploymentManager); 36 | const onStateReceiveTxn = await ( 37 | await fxChild.connect(mumbaiReceiverSigner).onStateReceive( 38 | 123, // stateId 39 | stateSyncedData, // _data 40 | { gasPrice: 0 } 41 | ) 42 | ).wait(); 43 | 44 | const proposalCreatedEvent = onStateReceiveTxn.events.find(event => event.address === bridgeReceiver.address); 45 | const { args: { id, eta } } = bridgeReceiver.interface.parseLog(proposalCreatedEvent); 46 | 47 | // fast forward l2 time 48 | await setNextBlockTimestamp(bridgeDeploymentManager, eta.toNumber() + 1); 49 | 50 | // execute queued proposal 51 | await setNextBaseFeeToZero(bridgeDeploymentManager); 52 | await bridgeReceiver.executeProposal(id, { gasPrice: 0 }); 53 | } -------------------------------------------------------------------------------- /scripts/build-spec.js: -------------------------------------------------------------------------------- 1 | const { readFile, writeFile } = require('fs/promises'); 2 | const stream = require('stream'); 3 | const { spawn } = require('child_process'); 4 | 5 | async function pandoc(doc) { 6 | let resolve, reject; 7 | let res = new Promise((resolve_, reject_) => { 8 | resolve = resolve_; 9 | reject = reject_; 10 | }); 11 | 12 | child = spawn('pandoc', ['-f', 'markdown', '-o', 'SPEC.pdf']); 13 | child.on('close', (code) => { 14 | if (code === 0) { 15 | resolve(); 16 | } else { 17 | reject(new Error(`exit code: ${code}`)); 18 | } 19 | }); 20 | child.stdout.on('data', (data) => { 21 | console.log('pandoc: ' + data); 22 | }); 23 | var stdinStream = new stream.Readable(); 24 | stdinStream.push(doc); 25 | stdinStream.push(null); 26 | stdinStream.pipe(child.stdin); 27 | 28 | return res; 29 | } 30 | 31 | async function run() { 32 | let file = await readFile('SPEC.md', 'utf8'); 33 | let innerRegex = /% preamble((.|\n)+?)(?=% postamble)/gim; 34 | let templateRegex = /% header %/; 35 | let postambleRegex = /\$\$\s*\n\s*% postamble\s*\n\s*\$\$/; 36 | 37 | let inner = innerRegex.exec(file); 38 | let template = templateRegex.exec(file); 39 | let preamble = inner[0]; 40 | 41 | preamble = preamble.replace( 42 | /(?<=[\]\}]\{\\(Config|Storage|ContractCall).*)(([a-z]+)|((? comet.getAssetInfo(i))); 35 | 36 | const liquidator = await dm.deploy( 37 | 'liquidator', 38 | 'liquidator/Liquidator.sol', 39 | [ 40 | RECIPIENT, // _recipient 41 | SWAP_ROUTER, // _swapRouter 42 | comet.address, // _comet 43 | UNISWAP_V3_FACTORY_ADDRESS, // _factory 44 | WETH9, // _WETH9 45 | 0, // _liquidationThreshold, 46 | assets.map(a => a.asset), // _assets 47 | assets.map(_a => false), // _lowLiquidityPools 48 | assets.map(_a => 500), // _poolFees 49 | ] 50 | ); 51 | 52 | console.log(`Liquidator deployed on ${network} @ ${liquidator.address}`); 53 | } 54 | 55 | main() 56 | .then(() => process.exit(0)) 57 | .catch((error) => { 58 | console.error(error); 59 | process.exit(1); 60 | }); -------------------------------------------------------------------------------- /scripts/liquidation_bot/index.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat'; 2 | import { DeploymentManager } from '../../plugins/deployment_manager/DeploymentManager'; 3 | import { 4 | CometInterface, 5 | Liquidator 6 | } from '../../build/types'; 7 | import liquidateUnderwaterBorrowers from './liquidateUnderwaterBorrowers'; 8 | 9 | const loopDelay = 5000; 10 | 11 | async function main() { 12 | let { DEPLOYMENT: deployment, LIQUIDATOR_ADDRESS: liquidatorAddress } = process.env; 13 | if (!liquidatorAddress) { 14 | throw new Error('missing required env variable: LIQUIDATOR_ADDRESS'); 15 | } 16 | if (!deployment) { 17 | throw new Error('missing required env variable: DEPLOYMENT'); 18 | } 19 | 20 | const network = hre.network.name; 21 | 22 | const dm = new DeploymentManager( 23 | network, 24 | deployment, 25 | hre, 26 | { 27 | writeCacheToDisk: false, 28 | verificationStrategy: 'eager', 29 | } 30 | ); 31 | await dm.spider(); 32 | 33 | const signer = await dm.getSigner(); 34 | const contracts = await dm.contracts(); 35 | const comet = contracts.get('comet') as CometInterface; 36 | 37 | if (!comet) { 38 | throw new Error(`no deployed Comet found for ${network}/${deployment}`); 39 | } 40 | 41 | const liquidator = await hre.ethers.getContractAt( 42 | 'Liquidator', 43 | liquidatorAddress, 44 | signer 45 | ) as Liquidator; 46 | 47 | let lastBlockNumber: number; 48 | 49 | while (true) { 50 | const currentBlockNumber = await hre.ethers.provider.getBlockNumber(); 51 | 52 | console.log(`currentBlockNumber: ${currentBlockNumber}`); 53 | 54 | if (currentBlockNumber !== lastBlockNumber) { 55 | lastBlockNumber = currentBlockNumber; 56 | await liquidateUnderwaterBorrowers( 57 | comet, 58 | liquidator, 59 | signer 60 | ); 61 | } else { 62 | console.log(`block already checked; waiting ${loopDelay}ms`); 63 | await new Promise(resolve => setTimeout(resolve, loopDelay)); 64 | } 65 | } 66 | } 67 | 68 | main() 69 | .then(() => process.exit(0)) 70 | .catch((error) => { 71 | console.error(error); 72 | process.exit(1); 73 | }); 74 | -------------------------------------------------------------------------------- /scripts/liquidation_bot/liquidateUnderwaterBorrowers.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; 2 | import { 3 | CometInterface, 4 | Liquidator 5 | } from '../../build/types'; 6 | 7 | const daiPool = { 8 | tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F', 9 | poolFee: 100 10 | }; 11 | 12 | async function attemptLiquidation( 13 | liquidator: Liquidator, 14 | signer: SignerWithAddress, 15 | targetAddresses: string[] 16 | ) { 17 | try { 18 | await liquidator.connect(signer).initFlash({ 19 | accounts: targetAddresses, 20 | pairToken: daiPool.tokenAddress, 21 | poolFee: daiPool.poolFee 22 | }); 23 | console.log(`Successfully liquidated ${targetAddresses}`); 24 | } catch (e) { 25 | console.log(`Failed to liquidate ${targetAddresses}`); 26 | console.log(e.message); 27 | } 28 | } 29 | 30 | async function getUniqueAddresses(comet: CometInterface): Promise> { 31 | const withdrawEvents = await comet.queryFilter(comet.filters.Withdraw()); 32 | return new Set(withdrawEvents.map(event => event.args.src)); 33 | } 34 | 35 | export default async function liquidateUnderwaterBorrowers( 36 | comet: CometInterface, 37 | liquidator: Liquidator, 38 | signer: SignerWithAddress 39 | ) { 40 | const uniqueAddresses = await getUniqueAddresses(comet); 41 | 42 | console.log(`${uniqueAddresses.size} unique addresses found`); 43 | 44 | for (const address of uniqueAddresses) { 45 | const isLiquidatable = await comet.isLiquidatable(address); 46 | 47 | console.log(`${address} isLiquidatable=${isLiquidatable}`); 48 | 49 | if (isLiquidatable) { 50 | await attemptLiquidation( 51 | liquidator, 52 | signer, 53 | [address] 54 | ); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /tasks/scenario/task.ts: -------------------------------------------------------------------------------- 1 | import { task } from 'hardhat/config'; 2 | import { runScenario } from '../../plugins/scenario/worker/Parent'; 3 | import hreForBase from '../../plugins/scenario/utils/hreForBase'; 4 | import '../../plugins/scenario/type-extensions'; 5 | import { ForkSpec } from '../../plugins/scenario/World'; 6 | import { HardhatRuntimeEnvironment } from 'hardhat/types'; 7 | import { DeploymentManager } from '../../plugins/deployment_manager/DeploymentManager'; 8 | import * as types from 'hardhat/internal/core/params/argumentTypes'; // TODO harhdat argument types not from internal 9 | 10 | function getBasesFromTaskArgs(givenBases: string | undefined, env: HardhatRuntimeEnvironment): ForkSpec[] { 11 | let bases: ForkSpec[] = env.config.scenario.bases; 12 | if (givenBases) { 13 | let baseMap = Object.fromEntries(env.config.scenario.bases.map((base) => [base.name, base])); 14 | bases = givenBases.split(',').map((baseName) => { 15 | let base = baseMap[baseName]; 16 | if (!base) { 17 | throw new Error(`Unknown base: ${baseName}`); 18 | } 19 | return base; 20 | }); 21 | } 22 | 23 | return bases; 24 | } 25 | 26 | task('scenario', 'Runs scenario tests') 27 | .addOptionalParam('bases', 'Bases to run on [defaults to all]') 28 | .addFlag('spider', 'run spider persistently before scenarios') 29 | .addOptionalParam('stall', 'milliseconds to wait until we fail for stalling', 240_000, types.int) 30 | .addOptionalParam('workers', 'count of workers', 1, types.int) // TODO: optimize parallelized workers better (1 per base?) 31 | .setAction(async (taskArgs, env: HardhatRuntimeEnvironment) => { 32 | const bases: ForkSpec[] = getBasesFromTaskArgs(taskArgs.bases, env); 33 | if (taskArgs.spider) { 34 | await env.run('scenario:spider', taskArgs); 35 | } 36 | await runScenario(env.config.scenario, bases, taskArgs.workers, taskArgs.workers > 1, taskArgs.stall); 37 | }); 38 | 39 | task('scenario:spider', 'Runs spider in preparation for scenarios') 40 | .addOptionalParam('bases', 'Bases to run on [defaults to all]') 41 | .setAction(async (taskArgs, env) => { 42 | const bases: ForkSpec[] = getBasesFromTaskArgs(taskArgs.bases, env); 43 | await Promise.all(bases.map(async (base) => { 44 | if (base.network !== 'hardhat') { 45 | let hre = hreForBase(base); 46 | let dm = new DeploymentManager( 47 | base.name, 48 | base.deployment, 49 | hre, 50 | { 51 | writeCacheToDisk: true, 52 | } 53 | ); 54 | await dm.spider(); 55 | } 56 | })); 57 | }); 58 | -------------------------------------------------------------------------------- /tasks/spider/task.ts: -------------------------------------------------------------------------------- 1 | import { task } from 'hardhat/config'; 2 | import { execSync } from 'child_process'; 3 | import { DeploymentManager } from '../../plugins/deployment_manager/DeploymentManager'; 4 | 5 | async function deleteSpiderArtifacts() { 6 | [ 7 | 'rm -rf deployments/*/.contracts', 8 | 'rm deployments/*/*/aliases.json', 9 | ].forEach(async (command) => { 10 | console.log(command); 11 | execSync(command); 12 | }); 13 | } 14 | 15 | task('spider', 'Use Spider method to pull in contract configs') 16 | .addFlag('clean', 'Deletes spider artifacts') 17 | .addOptionalParam('deployment', 'The deployment to spider') 18 | .setAction(async ({ clean, deployment }, hre) => { 19 | const network = hre.network.name; 20 | 21 | if (clean) { 22 | await deleteSpiderArtifacts(); 23 | } else { 24 | if (!deployment) { 25 | throw new Error('missing argument --deployment'); 26 | } 27 | let dm = new DeploymentManager( 28 | network, 29 | deployment, 30 | hre, 31 | { 32 | writeCacheToDisk: true, 33 | } 34 | ); 35 | await dm.spider(); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /test/allow-test.ts: -------------------------------------------------------------------------------- 1 | import { ethers, event, expect, makeProtocol, wait } from './helpers'; 2 | 3 | describe('allow', function () { 4 | it('isAllowed defaults to false', async () => { 5 | const { comet } = await makeProtocol(); 6 | const [_admin, user, manager] = await ethers.getSigners(); 7 | const userAddress = user.address; 8 | const managerAddress = manager.address; 9 | 10 | expect(await comet.isAllowed(userAddress, managerAddress)).to.be.false; 11 | }); 12 | 13 | it('allows a user to authorize a manager', async () => { 14 | const { comet } = await makeProtocol(); 15 | const [_admin, user, manager] = await ethers.getSigners(); 16 | const userAddress = user.address; 17 | const managerAddress = manager.address; 18 | 19 | const tx = await wait(comet.connect(user).allow(managerAddress, true)); 20 | 21 | expect(await comet.isAllowed(userAddress, managerAddress)).to.be.true; 22 | expect(event(tx, 0)).to.be.deep.equal({ 23 | Approval: { 24 | owner: userAddress, 25 | spender: managerAddress, 26 | amount: ethers.constants.MaxUint256.toBigInt(), 27 | } 28 | }); 29 | }); 30 | 31 | it('allows a user to rescind authorization', async () => { 32 | const { comet } = await makeProtocol(); 33 | const [_admin, user, manager] = await ethers.getSigners(); 34 | const userAddress = user.address; 35 | const managerAddress = manager.address; 36 | 37 | const _authorizeTx = await wait(comet.connect(user).allow(managerAddress, true)); 38 | 39 | expect(await comet.isAllowed(userAddress, managerAddress)).to.be.true; 40 | 41 | const rescindTx = await wait(comet.connect(user).allow(managerAddress, false)); 42 | 43 | expect(await comet.isAllowed(userAddress, managerAddress)).to.be.false; 44 | expect(event(rescindTx, 0)).to.be.deep.equal({ 45 | Approval: { 46 | owner: userAddress, 47 | spender: managerAddress, 48 | amount: 0n, 49 | } 50 | }); 51 | }); 52 | }); 53 | 54 | describe('hasPermission', function () { 55 | it('is true for self', async () => { 56 | const { comet, users: [alice] } = await makeProtocol(); 57 | expect(await comet.hasPermission(alice.address, alice.address)).to.be.true; 58 | }); 59 | 60 | it('is false by default for others', async () => { 61 | const { comet, users: [alice, bob] } = await makeProtocol(); 62 | expect(await comet.hasPermission(alice.address, bob.address)).to.be.false; 63 | }); 64 | 65 | it('is true when user is allowed', async () => { 66 | const { comet, users: [alice, bob] } = await makeProtocol(); 67 | await comet.connect(alice).allow(bob.address, true); 68 | expect(await comet.hasPermission(alice.address, bob.address)).to.be.true; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/balance-test.ts: -------------------------------------------------------------------------------- 1 | import { expect, makeProtocol, setTotalsBasic } from './helpers'; 2 | 3 | describe('totalBorrow', function () { 4 | it('has correct totalBorrow', async () => { 5 | const { comet } = await makeProtocol(); 6 | await setTotalsBasic(comet, { 7 | baseBorrowIndex: 2e15, 8 | totalBorrowBase: 50e6, 9 | }); 10 | expect(await comet.totalBorrow()).to.eq(100e6); 11 | }); 12 | }); 13 | 14 | describe('borrowBalanceOf', function () { 15 | it('returns borrow amount (when principal amount is negative)', async () => { 16 | const { comet, users: [user] } = await makeProtocol(); 17 | await setTotalsBasic(comet, { 18 | baseSupplyIndex: 2e15, 19 | baseBorrowIndex: 3e15, 20 | }); 21 | await comet.setBasePrincipal(user.address, -100e6); // borrow of $100 USDC 22 | const borrowBalanceOf = await comet.borrowBalanceOf(user.address); 23 | expect(borrowBalanceOf).to.eq(300e6); // baseSupplyIndex = 3e15 24 | }); 25 | 26 | it('returns 0 when principal amount is positive', async () => { 27 | const { comet, users: [user] } = await makeProtocol(); 28 | await setTotalsBasic(comet, { 29 | baseSupplyIndex: 2e15, 30 | baseBorrowIndex: 3e15, 31 | }); 32 | await comet.setBasePrincipal(user.address, 100e6); 33 | const borrowBalanceOf = await comet.borrowBalanceOf(user.address); 34 | expect(borrowBalanceOf).to.eq(0); 35 | }); 36 | }); 37 | 38 | // XXX test implicit interest accrual explicitly -------------------------------------------------------------------------------- /test/comet-ext-test.ts: -------------------------------------------------------------------------------- 1 | import { CometHarnessInterface, FaucetToken } from '../build/types'; 2 | import { expect, exp, makeProtocol, setTotalsBasic } from './helpers'; 3 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; 4 | 5 | describe('CometExt', function () { 6 | let comet: CometHarnessInterface; 7 | let user: SignerWithAddress; 8 | let tokens: { [symbol: string]: FaucetToken }; 9 | 10 | beforeEach(async () => { 11 | ({ 12 | comet, 13 | users: [user], 14 | tokens, 15 | } = await makeProtocol()); 16 | 17 | // Set different indices 18 | await setTotalsBasic(comet, { 19 | baseSupplyIndex: 2e15, 20 | baseBorrowIndex: 3e15, 21 | }); 22 | }); 23 | 24 | it('returns factor scale', async () => { 25 | const factorScale = await comet.factorScale(); 26 | expect(factorScale).to.eq(exp(1, 18)); 27 | }); 28 | 29 | it('returns price scale', async () => { 30 | const priceScale = await comet.priceScale(); 31 | expect(priceScale).to.eq(exp(1, 8)); 32 | }); 33 | 34 | it('returns collateralBalance (in units of the collateral asset)', async () => { 35 | const { WETH } = tokens; 36 | 37 | await comet.setCollateralBalance( 38 | user.address, 39 | WETH.address, 40 | exp(5, 18) 41 | ); 42 | 43 | const collateralBalanceOf = await comet.collateralBalanceOf( 44 | user.address, 45 | WETH.address 46 | ); 47 | expect(collateralBalanceOf).to.eq(exp(5,18)); 48 | }); 49 | }); -------------------------------------------------------------------------------- /test/governor-simple-test.ts: -------------------------------------------------------------------------------- 1 | import { GovernorSimple__factory } from '../build/types'; 2 | import { ethers } from 'hardhat'; 3 | import { expect } from 'chai'; 4 | 5 | async function buildGovernorSimple() { 6 | const GovernorSimpleFactory = (await ethers.getContractFactory('GovernorSimple')) as GovernorSimple__factory; 7 | const governorSimple = await GovernorSimpleFactory.deploy(); 8 | await governorSimple.deployed(); 9 | return governorSimple; 10 | } 11 | 12 | describe('GovernorSimple', function () { 13 | it('adds a new admin', async () => { 14 | const [alice, bob] = await ethers.getSigners(); 15 | const governorSimple = await buildGovernorSimple(); 16 | await governorSimple.initialize( 17 | ethers.constants.AddressZero, 18 | [alice.address] 19 | ); 20 | 21 | expect(await governorSimple.isAdmin(bob.address)).to.be.false; 22 | 23 | await governorSimple.connect(alice).addAdmin(bob.address); 24 | 25 | expect(await governorSimple.isAdmin(bob.address)).to.be.true; 26 | }); 27 | 28 | it('removes an existing admin', async () => { 29 | const [alice, bob] = await ethers.getSigners(); 30 | const governorSimple = await buildGovernorSimple(); 31 | await governorSimple.initialize( 32 | ethers.constants.AddressZero, 33 | [alice.address, bob.address] 34 | ); 35 | 36 | expect(await governorSimple.isAdmin(bob.address)).to.be.true; 37 | 38 | await governorSimple.connect(alice).removeAdmin(bob.address); 39 | 40 | expect(await governorSimple.isAdmin(bob.address)).to.be.false; 41 | }); 42 | }); -------------------------------------------------------------------------------- /test/liquidation/addresses.ts: -------------------------------------------------------------------------------- 1 | // mainnet asset whales 2 | export const DAI_WHALE = '0x7a8edc710ddeadddb0b539de83f3a306a621e823'; 3 | export const USDC_WHALE = '0xA929022c9107643515F5c777cE9a910F0D1e490C'; 4 | export const WETH_WHALE = '0x0F4ee9631f4be0a63756515141281A3E2B293Bbe'; 5 | export const COMP_WHALE = '0x73af3bcf944a6559933396c1577b257e2054d935'; 6 | export const WBTC_WHALE = '0x602d9abd5671d24026e2ca473903ff2a9a957407'; 7 | export const UNI_WHALE = '0x7D325A9C8F10758188641FE91cFD902499edC782'; 8 | export const LINK_WHALE = '0x6262998ced04146fa42253a5c0af90ca02dfd2a3'; 9 | 10 | // mainnet assets 11 | export const WBTC = '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599'; 12 | export const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; 13 | export const USDT = '0xdac17f958d2ee523a2206206994597c13d831ec7'; 14 | export const COMP = '0xc00e94cb662c3520282e6f5717214004a7f26888'; 15 | export const LINK = '0x514910771AF9Ca656af840dff83E8264EcF986CA'; 16 | export const DAI = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; 17 | export const UNI = '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'; 18 | export const WETH9 = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; 19 | 20 | // Uniwap V3 contracts 21 | export const SWAP_ROUTER = '0xE592427A0AEce92De3Edee1F18E0157C05861564'; 22 | export const UNISWAP_V3_FACTORY = '0x1F98431c8aD98523631AE4a59f267346ea31F984'; 23 | 24 | // Chainlink mainnet price feeds 25 | export const DAI_USDC_PRICE_FEED = '0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9'; 26 | export const USDC_USD_PRICE_FEED = '0x8fffffd4afb6115b954bd326cbe7b4ba576818f6'; 27 | export const ETH_USDC_PRICE_FEED = '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419'; 28 | export const WBTC_USDC_PRICE_FEED = '0xf4030086522a5beea4988f8ca5b36dbc97bee88c'; 29 | export const COMP_USDC_PRICE_FEED = '0xdbd020caef83efd542f4de03e3cf0c28a4428bd5'; 30 | export const LINK_USDC_PRICE_FEED = '0x2c1d072e956affc0d435cb7ac38ef18d24d9127c'; 31 | export const UNI_USDC_PRICE_FEED = '0x553303d460ee0afb37edff9be42922d8ff63220e'; 32 | -------------------------------------------------------------------------------- /test/liquidation/liquidation-bot-script.ts: -------------------------------------------------------------------------------- 1 | import { expect, exp } from '../helpers'; 2 | import liquidateUnderwaterBorrowers from '../../scripts/liquidation_bot/liquidateUnderwaterBorrowers'; 3 | import makeLiquidatableProtocol, { forkMainnet, resetHardhatNetwork } from './makeLiquidatableProtocol'; 4 | 5 | describe('Liquidation Bot', function () { 6 | before(forkMainnet); 7 | after(resetHardhatNetwork); 8 | 9 | describe('liquidateUnderwaterBorrowers', function () { 10 | it('liquidates underwater borrowers', async function () { 11 | const { 12 | comet, 13 | liquidator, 14 | users: [signer, underwater], 15 | assets: { dai, usdc }, 16 | whales: { usdcWhale } 17 | } = await makeLiquidatableProtocol(); 18 | 19 | // transfer USDC to comet, so it has money to pay out withdraw to underwater user 20 | await usdc.connect(usdcWhale).transfer(comet.address, 300000000n); // 300e6 21 | await dai.connect(underwater).approve(comet.address, 120000000000000000000n); 22 | await comet.connect(underwater).supply(dai.address, 120000000000000000000n); 23 | // withdraw to ensure that there is a Withdraw event for the user 24 | await comet.connect(underwater).withdraw(usdc.address, 10e6); 25 | // put the position underwater 26 | await comet.setBasePrincipal(underwater.address, -(exp(200, 6))); 27 | 28 | expect(await comet.isLiquidatable(underwater.address)).to.be.true; 29 | 30 | await liquidateUnderwaterBorrowers( 31 | comet, 32 | liquidator, 33 | signer 34 | ); 35 | 36 | expect(await comet.isLiquidatable(underwater.address)).to.be.false; 37 | }); 38 | }); 39 | }); -------------------------------------------------------------------------------- /test/price-feed-test.ts: -------------------------------------------------------------------------------- 1 | import { expect, makeProtocol } from './helpers'; 2 | 3 | describe('getPrice', function () { 4 | it('returns price data for assets, with 8 decimals', async () => { 5 | const { comet, priceFeeds } = await makeProtocol({ 6 | assets: { 7 | USDC: {}, 8 | COMP: { 9 | initial: 1e7, 10 | decimals: 18, 11 | initialPrice: 1.2345, 12 | }, 13 | }, 14 | }); 15 | 16 | const price = await comet.getPrice(priceFeeds.COMP.address); 17 | 18 | expect(price.toNumber()).to.equal(123450000); 19 | }); 20 | 21 | it('reverts if given a bad priceFeed address', async () => { 22 | const { comet } = await makeProtocol(); 23 | 24 | // COMP on mainnet (not a legit price feed address) 25 | const invalidPriceFeedAddress = '0xc00e94cb662c3520282e6f5717214004a7f26888'; 26 | 27 | await expect(comet.getPrice(invalidPriceFeedAddress)).to.be.reverted; 28 | }); 29 | 30 | it('reverts if price feed returns negative value', async () => { 31 | const { comet, priceFeeds } = await makeProtocol({ 32 | assets: { 33 | USDC: {}, 34 | COMP: { 35 | initial: 1e7, 36 | decimals: 18, 37 | initialPrice: -1, 38 | }, 39 | }, 40 | }); 41 | 42 | await expect(comet.getPrice(priceFeeds.COMP.address)).to.be.revertedWith("custom error 'BadPrice()'"); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/reserves-test.ts: -------------------------------------------------------------------------------- 1 | import { expect, makeProtocol, setTotalsBasic } from './helpers'; 2 | 3 | describe('getReserves', function () { 4 | it('calculates 0 reserves', async () => { 5 | const protocol = await makeProtocol({base: 'USDC'}); 6 | const { comet, tokens } = protocol; 7 | const { USDC } = tokens; 8 | await USDC.allocateTo(comet.address, 100); 9 | 10 | await setTotalsBasic(comet, { 11 | baseSupplyIndex: 4e15, 12 | baseBorrowIndex: 3e15, 13 | totalSupplyBase: 25n, 14 | totalBorrowBase: 0n, 15 | }); 16 | 17 | const reserves = await comet.getReserves(); 18 | 19 | expect(reserves).to.be.equal(0n); 20 | }); 21 | 22 | it('calculates positive reserves', async () => { 23 | const protocol = await makeProtocol({base: 'USDC'}); 24 | const { comet, tokens } = protocol; 25 | const { USDC } = tokens; 26 | await USDC.allocateTo(comet.address, 100); 27 | 28 | await setTotalsBasic(comet, { 29 | baseSupplyIndex: 2e15, 30 | baseBorrowIndex: 5e15, 31 | totalSupplyBase: 50n, 32 | totalBorrowBase: 10n, 33 | }); 34 | 35 | const reserves = await comet.getReserves(); 36 | 37 | expect(reserves).to.be.equal(50n); 38 | }); 39 | 40 | it('calculates negative reserves', async () => { 41 | const protocol = await makeProtocol({base: 'USDC'}); 42 | const { comet } = protocol; 43 | 44 | // Protocol holds no USDC 45 | 46 | await setTotalsBasic(comet, { 47 | baseSupplyIndex: 2e15, 48 | baseBorrowIndex: 3e15, 49 | totalSupplyBase: 50n, 50 | totalBorrowBase: 0n, 51 | }); 52 | 53 | const reserves = await comet.getReserves(); 54 | 55 | expect(reserves).to.be.equal(-100n); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/sanity-test.ts: -------------------------------------------------------------------------------- 1 | import { ethers, expect, makeProtocol } from './helpers'; 2 | 3 | describe('getNow', function () { 4 | it('reverts if timestamp overflows', async () => { 5 | const { comet } = await makeProtocol(); 6 | await ethers.provider.send('evm_mine', [2**40]); 7 | await expect(comet.getNow()).to.be.revertedWith("custom error 'TimestampTooLarge()'"); 8 | await ethers.provider.send('hardhat_reset', []); // dont break downstream tests... 9 | }); 10 | }); 11 | 12 | describe('updateBaseBalance', function () { 13 | // XXX 14 | it.skip('accrues the right amount of rewards', async () => { 15 | // XXX 16 | }); 17 | }); 18 | 19 | -------------------------------------------------------------------------------- /test/scen-test.ts: -------------------------------------------------------------------------------- 1 | /* Check basic scenario compilation */ 2 | 3 | // import { Runner } from '../plugins/scenario/Runner'; 4 | // import { CometContext } from '../scenario/context/CometContext'; 5 | 6 | // const scenarios = []; 7 | // XXX Get this to work 8 | // new Runner({ 9 | // bases: [ 10 | // { 11 | // name: 'development', 12 | // }, 13 | // ], 14 | // }) 15 | // .run(scenarios, (...args) => console.log('Result', args)) 16 | // .then((r) => { 17 | // /* console.trace(r) */ 18 | // }) 19 | // .catch((e) => { 20 | // throw e; 21 | // }); 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "outDir": "dist", 7 | "moduleResolution": "node", 8 | "declaration": true 9 | }, 10 | "include": ["./scripts", "./test", "./tasks"], 11 | "files": [ 12 | "./index.ts", 13 | "./hardhat.config.ts", 14 | "node_modules/@nomiclabs/hardhat-ethers/internal/type-extensions.d.ts", 15 | "node_modules/@nomiclabs/hardhat-waffle/dist/src/type-extensions.d.ts", 16 | "node_modules/@typechain/hardhat/dist/type-extensions.d.ts" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------