├── .gitignore ├── .nvmrc ├── .solcover.js ├── .soliumignore ├── .soliumrc.json ├── LICENSE ├── README.md ├── audit ├── oracles.md └── rLending_Security_Audit_Final_Report_v210303.pdf ├── calcSize.js ├── contracts ├── BaseJumpRateModelV2.sol ├── CCompLikeDelegate.sol ├── CErc20.sol ├── CErc20Delegate.sol ├── CErc20Delegator.sol ├── CErc20Immutable.sol ├── CRBTC.sol ├── CRBTCCompanion.sol ├── CRBTCCompanionInterface.sol ├── CRDOC.sol ├── CToken.sol ├── CTokenInterfaces.sol ├── CarefulMath.sol ├── Comptroller.sol ├── ComptrollerG1.sol ├── ComptrollerG2.sol ├── ComptrollerG3.sol ├── ComptrollerG4.sol ├── ComptrollerG5.sol ├── ComptrollerG6.sol ├── ComptrollerInterface.sol ├── ComptrollerStorage.sol ├── EIP20Interface.sol ├── EIP20NonStandardInterface.sol ├── ERC20.sol ├── ErrorReporter.sol ├── Exponential.sol ├── ExponentialNoError.sol ├── Governance │ ├── GovernorAlpha.sol │ └── TROP.sol ├── HurricaneInterestRateModel.sol ├── InterestRateModel.sol ├── JumpRateModel.sol ├── JumpRateModelV2.sol ├── LegacyInterestRateModel.sol ├── LegacyJumpRateModelV2.sol ├── Lens │ └── TropykusLens.sol ├── Maximillion.sol ├── MultiSigWallet.sol ├── PriceOracle.sol ├── PriceOracleAdapter.sol ├── PriceOracleAdapterMoc.sol ├── PriceOracleProxy.sol ├── Reservoir.sol ├── SafeMath.sol ├── SignedSafeMath.sol ├── Timelock.sol ├── Unitroller.sol ├── WhitePaperInterestRateModel.sol └── mocks │ ├── MockPriceProviderMoC.sol │ ├── PriceOracleAdapterCompound.sol │ └── SimplePriceOracle.sol ├── datagen.js ├── deploy ├── config.js ├── deploy.js ├── mainDeploy.js └── programaticDeploy.js ├── error.js ├── gasCosts.json ├── hardhat.config.js ├── hardhat.networks.js ├── instructions ├── jest.config.js ├── package.json ├── reporterConfig.json ├── saddle.config.js ├── scenario ├── Grammar.pegjs ├── SCENARIO.md ├── package.json ├── script │ ├── generate_parser │ ├── repl │ ├── tsc │ └── webpack ├── src │ ├── Accounts.ts │ ├── Action.ts │ ├── Assert.ts │ ├── Builder │ │ ├── CTokenBuilder.ts │ │ ├── CTokenDelegateBuilder.ts │ │ ├── CompBuilder.ts │ │ ├── ComptrollerImplBuilder.ts │ │ ├── Erc20Builder.ts │ │ ├── GovernorBuilder.ts │ │ ├── InterestRateModelBuilder.ts │ │ ├── MaximillionBuilder.ts │ │ ├── PriceOracleBuilder.ts │ │ ├── PriceOracleProxyBuilder.ts │ │ ├── TimelockBuilder.ts │ │ └── UnitrollerBuilder.ts │ ├── Command.ts │ ├── Completer.ts │ ├── Contract.ts │ ├── Contract │ │ ├── CErc20Delegate.ts │ │ ├── CErc20Delegator.ts │ │ ├── CToken.ts │ │ ├── Comptroller.ts │ │ ├── ComptrollerImpl.ts │ │ ├── Counter.ts │ │ ├── Erc20.ts │ │ ├── Governor.ts │ │ ├── InterestRateModel.ts │ │ ├── Maximillion.ts │ │ ├── Pot.ts │ │ ├── PriceOracle.ts │ │ ├── PriceOracleAdapterCompound.ts │ │ ├── PriceOracleProxy.ts │ │ ├── Reservoir.ts │ │ ├── TROP.ts │ │ ├── Timelock.ts │ │ ├── TropykusLens.ts │ │ ├── Unitroller.ts │ │ ├── Vat.ts │ │ └── builder.js │ ├── ContractLookup.ts │ ├── CoreEvent.ts │ ├── CoreValue.ts │ ├── Encoding.ts │ ├── ErrorReporter.ts │ ├── ErrorReporterConstants.ts │ ├── Event.ts │ ├── Event │ │ ├── AssertionEvent.ts │ │ ├── CTokenDelegateEvent.ts │ │ ├── CTokenEvent.ts │ │ ├── CompEvent.ts │ │ ├── ComptrollerEvent.ts │ │ ├── ComptrollerImplEvent.ts │ │ ├── Erc20Event.ts │ │ ├── ExpectationEvent.ts │ │ ├── GovGuardianEvent.ts │ │ ├── GovernorEvent.ts │ │ ├── InterestRateModelEvent.ts │ │ ├── InvariantEvent.ts │ │ ├── MaximillionEvent.ts │ │ ├── PriceOracleEvent.ts │ │ ├── PriceOracleProxyEvent.ts │ │ ├── ProposalEvent.ts │ │ ├── TimelockEvent.ts │ │ ├── TrxEvent.ts │ │ └── UnitrollerEvent.ts │ ├── EventBuilder.ts │ ├── Expectation.ts │ ├── Expectation │ │ ├── ChangesExpectation.ts │ │ └── RemainsExpectation.ts │ ├── File.ts │ ├── Formatter.ts │ ├── Help.ts │ ├── HistoricReadline.ts │ ├── Hypothetical.ts │ ├── Invariant.ts │ ├── Invariant │ │ ├── RemainsInvariant.ts │ │ ├── StaticInvariant.ts │ │ └── SuccessInvariant.ts │ ├── Invokation.ts │ ├── Macro.ts │ ├── Networks.ts │ ├── Parser.ts │ ├── Printer.ts │ ├── Repl.d.ts │ ├── Repl.ts │ ├── Runner.ts │ ├── Settings.ts │ ├── Utils.ts │ ├── Value.ts │ ├── Value │ │ ├── CTokenDelegateValue.ts │ │ ├── CTokenValue.ts │ │ ├── CompValue.ts │ │ ├── ComptrollerImplValue.ts │ │ ├── ComptrollerValue.ts │ │ ├── Erc20Value.ts │ │ ├── GovernorValue.ts │ │ ├── InterestRateModelValue.ts │ │ ├── MCDValue.ts │ │ ├── MaximillionValue.ts │ │ ├── PriceOracleProxyValue.ts │ │ ├── PriceOracleValue.ts │ │ ├── ProposalValue.ts │ │ ├── TimelockValue.ts │ │ ├── UnitrollerValue.ts │ │ └── UserValue.ts │ ├── Verify.ts │ ├── Web.ts │ └── World.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── script ├── README.md ├── build_scenarios ├── compile ├── coverage ├── lint ├── rsk ├── saddle │ ├── comptrollerSize.js │ ├── deployToken.js │ ├── flywheelInit.js │ ├── gasReport.js │ ├── matchToken.js │ ├── support │ │ └── tokenConfig.js │ └── verifyToken.js ├── scen │ ├── deploy.scen │ └── scriptFlywheel.scen └── test ├── spec ├── scenario │ ├── AddReserves.scen │ ├── Borrow.scen │ ├── Borrow.scen.old │ ├── BorrowBalance.scen │ ├── BorrowCap.scen │ ├── BorrowEth.scen │ ├── BorrowWBTC.scen │ ├── BreakLiquidate.scen │ ├── CTokenAdmin.scen │ ├── ChangeDelegate.scen │ ├── CoreMacros │ ├── EnterExitMarkets.scen │ ├── Excel.scen.old │ ├── ExchangeRate.scen │ ├── Fee.scen │ ├── Flywheel │ │ ├── CompSpeed.scen │ │ ├── Flywheel.scen │ │ ├── Grants.scen │ │ └── Reservoir.scen │ ├── Governor │ │ ├── Cancel.scen │ │ ├── Defeat.scen │ │ ├── Execute.scen │ │ ├── Guardian.scen │ │ ├── Propose.scen │ │ ├── Queue.scen │ │ ├── Upgrade.scen │ │ └── Vote.scen │ ├── HypotheticalAccountLiquidity.scen │ ├── InKindLiquidation.scen │ ├── Liquidate.scen.old │ ├── Mint.scen │ ├── MintEth.scen │ ├── MintWBTC.scen │ ├── PriceOracleProxy.scen │ ├── RLEN │ │ └── RLEN.scen │ ├── ReEntry.scen │ ├── Redeem.scen │ ├── RedeemEth.scen │ ├── RedeemUnderlying.scen │ ├── RedeemUnderlyingEth.scen │ ├── RedeemUnderlyingWBTC.scen │ ├── RedeemWBTC.scen │ ├── ReduceReserves.scen │ ├── RepayBorrow.scen │ ├── RepayBorrowEth.scen │ ├── RepayBorrowWBTC.scen │ ├── Seize.scen │ ├── SetComptroller.scen │ ├── Supply.scen.old │ ├── SweepToken.scen │ ├── Tether.scen │ ├── Timelock.scen │ ├── TokenTransfer.scen │ ├── Unitroller.scen │ └── Withdraw.scen.old └── sim │ ├── 0001-comp-distribution-patch │ ├── hypothetical_upgrade.scen │ ├── kovan │ │ ├── execute.scen │ │ └── queue.scen │ └── ropsten │ │ ├── execute.scen │ │ └── queue.scen │ ├── 0003-borrow-caps-patch │ ├── deploy.scen │ ├── hypothetical_upgrade.scen │ ├── hypothetical_upgrade_post_deploy.scen │ └── hypothetical_upgrade_post_propose.scen │ ├── 0004-cusdc-irm-update │ └── hypothetical_upgrade.scen │ ├── 0005-grants │ ├── deploy.scen │ ├── hypothetical_upgrade.scen │ ├── hypothetical_upgrade_multiple_speed.scen │ └── hypothetical_upgrade_post_propose.scen │ ├── 0006-setspeed-manual-claims │ ├── deploy.scen │ ├── hypothetical_upgrade.scen │ ├── hypothetical_upgrade_borrower.scen │ ├── hypothetical_upgrade_post_deploy.scen │ └── hypothetical_upgrade_post_propose.scen │ ├── 0007-cdai-impl-migration │ ├── hypothetical_migration.scen │ └── hypothetical_migration_post_propose.scen │ └── 0008-sweep-token │ ├── hypothetical_migration.scen │ ├── hypothetical_migration_post_deploy.scen │ └── hypothetical_migration_post_propose.scen ├── tests ├── CompilerTest.js ├── Comptroller │ ├── accountLiquidityTest.js │ ├── adminTest.js │ ├── assetsListTest.js │ ├── comptrollerTest.js │ ├── liquidateCalculateAmountSeizeTest.js │ ├── pauseGuardianTest.js │ ├── proxiedComptrollerV1Test.js │ └── unitrollerTest.js ├── Contracts │ ├── CErc20Harness.sol │ ├── CRBTCHarness.sol │ ├── CRDOCHarness.sol │ ├── CompHarness.sol │ ├── ComptrollerHarness.sol │ ├── ComptrollerScenario.sol │ ├── ComptrollerScenarioG1.sol │ ├── ComptrollerScenarioG2.sol │ ├── ComptrollerScenarioG3.sol │ ├── ComptrollerScenarioG4.sol │ ├── ComptrollerScenarioG5.sol │ ├── ComptrollerScenarioG6.sol │ ├── Const.sol │ ├── Counter.sol │ ├── EvilToken.sol │ ├── FalseMarker.sol │ ├── FaucetToken.sol │ ├── Fauceteer.sol │ ├── FeeToken.sol │ ├── FixedPriceOracle.sol │ ├── GovernorAlphaHarness.sol │ ├── InterestRateModelHarness.sol │ ├── MathHelpers.sol │ ├── MockMCD.sol │ ├── PriceOracleProxyExtends.sol │ ├── Structs.sol │ ├── TetherInterface.sol │ ├── TimelockHarness.sol │ └── WBTC.sol ├── Errors.js ├── Flywheel │ ├── FlywheelTest.js │ └── GasTest.js ├── Fuzz │ └── CompWheelFuzzTest.js ├── Governance │ ├── CompScenarioTest.js │ ├── CompTest.js │ └── GovernorAlpha │ │ ├── CastVoteTest.js │ │ ├── ProposeTest.js │ │ ├── QueueTest.js │ │ └── StateTest.js ├── Jest.js ├── Lens │ └── CompoundLensTest.js ├── Matchers.js ├── MaximillionTest.js ├── Models │ └── InterestRateModelTest.js ├── MultiSigWalletTest.js ├── PriceOracleProxyTest.js ├── Scenario.js ├── Scenarios │ ├── AddReservesScenTest.js │ ├── BorrowBalanceScenTest.js │ ├── BorrowCapScenTest.js │ ├── BorrowEthScenTest.js │ ├── BorrowScenTest.js │ ├── BorrowWBTCScenTest.js │ ├── BreakLiquidateScenTest.js │ ├── CTokenAdminScenTest.js │ ├── ChangeDelegateScenTest.js │ ├── EnterExitMarketsScenTest.js │ ├── ExchangeRateScenTest.js │ ├── FeeScenTest.js │ ├── Flywheel │ │ ├── CompSpeedScenTest.js │ │ ├── FlywheelScenTest.js │ │ ├── GrantsScenTest.js │ │ └── ReservoirScenTest.js │ ├── Governor │ │ ├── CancelScenTest.js │ │ ├── DefeatScenTest.js │ │ ├── ExecuteScenTest.js │ │ ├── GuardianScenTest.js │ │ ├── ProposeScenTest.js │ │ ├── QueueScenTest.js │ │ ├── UpgradeScenTest.js │ │ └── VoteScenTest.js │ ├── HypotheticalAccountLiquidityScenTest.js │ ├── InKindLiquidationScenTest.js │ ├── MintEthScenTest.js │ ├── MintScenTest.js │ ├── MintWBTCScenTest.js │ ├── PriceOracleProxyScenTest.js │ ├── RLEN │ │ └── RLENScenTest.js │ ├── ReEntryScenTest.js │ ├── RedeemEthScenTest.js │ ├── RedeemScenTest.js │ ├── RedeemUnderlyingEthScenTest.js │ ├── RedeemUnderlyingScenTest.js │ ├── RedeemUnderlyingWBTCScenTest.js │ ├── RedeemWBTCScenTest.js │ ├── ReduceReservesScenTest.js │ ├── RepayBorrowEthScenTest.js │ ├── RepayBorrowScenTest.js │ ├── RepayBorrowWBTCScenTest.js │ ├── SeizeScenTest.js │ ├── SetComptrollerScenTest.js │ ├── SweepTokenScenTest.js │ ├── TetherScenTest.js │ ├── TimelockScenTest.js │ ├── TokenTransferScenTest.js │ └── UnitrollerScenTest.js ├── Simulations │ └── kSATHurricane.js ├── SpinaramaTest.js ├── TimelockTest.js ├── Tokens │ ├── accrueInterestTest.js │ ├── adminTest.js │ ├── borrowAndRepayCEtherTest.js │ ├── borrowAndRepayTest.js │ ├── cTokenTest.js │ ├── compLikeTest.js │ ├── liquidateTest.js │ ├── mintAndRedeemCEtherTest.js │ ├── mintAndRedeemTest.js │ ├── reservesTest.js │ ├── safeTokenTest.js │ ├── setComptrollerTest.js │ ├── setInterestRateModelTest.js │ └── transferTest.js ├── Tropykus │ ├── BlocksPerYearUpdateTest.js │ ├── InterestRateModelsTest.js │ ├── ZeroCheckTest.js │ ├── kRBTCTest.js │ ├── kRDOCTest.js │ ├── kSATTest.js │ └── kSATWithdrawAllFundsTest.js ├── Utils │ ├── Compound.js │ ├── EIP712.js │ ├── Ethereum.js │ ├── InfuraProxy.js │ └── JS.js └── gasProfiler.js └── yarn.lock /.nvmrc: -------------------------------------------------------------------------------- 1 | v12.19.0 2 | engineStrict=true 3 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process'); 2 | async function moveCoverage(config) { 3 | execSync('mv ./.coverage_artifacts/contracts ./networks/coverage-contracts'); 4 | } 5 | 6 | async function moveCoverageBack() { 7 | execSync('mv ./networks/coverage-contracts ./.coverage_artifacts/contracts'); 8 | } 9 | 10 | module.exports = { 11 | port: 8555, 12 | providerOpts: 13 | { // See example coverage settings at https://github.com/sc-forks/solidity-coverage 14 | gas: 0xfffffff, 15 | gasPrice: 0x01 16 | }, 17 | mocha: { 18 | enableTimeouts: false, 19 | grep: /@gas|@no-cov/, 20 | invert: true 21 | }, 22 | onCompileComplete: moveCoverage, 23 | onTestsComplete: moveCoverageBack, 24 | skipFiles: ['test'].concat( 25 | process.env['SKIP_UNITROLLER'] ? ['Unitroller.sol'] : []), 26 | }; 27 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/contracts/WBTC.sol 3 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": [ 4 | "security" 5 | ], 6 | "rules": { 7 | "quotes": [ 8 | "error", 9 | "double" 10 | ], 11 | "indentation": "off", 12 | "max-len": [ 13 | "error", 14 | 200 15 | ], 16 | "security/no-block-members": "off", 17 | "security/no-inline-assembly": "off", 18 | "security/no-low-level-calls": "off", 19 | "security/no-tx-origin": "off", 20 | "imports-on-top": "off" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /audit/oracles.md: -------------------------------------------------------------------------------- 1 | # Oracles 2 | Given that we foresee the possibility of diverse Oracle systems being deployed in the network, we developed a middle-layer contract adapter system to interact with each Oracle as an intermediary. This way, the tropykus Protocol is capable of receiving price information from every kind of Oracle, regardless of its implementation. 3 | 4 | ![alt text](../whitepaper/LaTeX/img/oracles.png "Oracle adapter layers diagram") 5 | 6 | Oracles are very important for DEFI solutions, so we use them as a critical part of this protocol. 7 | tropykus will launch using the [Money on Chain v1 oracles](https://github.com/money-on-chain/Amphiraos-Oracle) that are currently on mainnet. The BTC price oracle is already in use by [Money on Chain protocol](https://alpha.moneyonchain.com/) and its tokens (DoC, BPro and BTCx). The RIF price oracle is already in use by [RIF on Chain protocol](https://rif.moneyonchain.com/) and its tokens (RDOC, RIFP and RIFX). Finally, for rUSDT price we are using a fixed price of 1 USD as the USDT can be exchanged by that amount and USDT in RSK is not yet listed on other exchanges besides [RSK Swap](https://rskswap.com/) 8 | As this is a centralized oracle run by a third party tropykus will be monitoring it. In case of abnormal results, it will be changed to another oracle solution or we will run our own. 9 | 10 | # Roadmap 11 | As RSK continues to grow so does the maturity of the oracles in it. Money on Chain will be launching in the near future their [v2 oracles called oMoC](https://developers.rsk.co/solutions/oraclemoneyonchain/). This new version will be decentralized, reduce costs and add economic incentives. tropykus will update to this new version when it's available on mainnet and register its own Oracle on the oMoC protocol 12 | [Chainlink](https://chain.link/) is also planning on launching [their oracles on RSK ](https://www.rifos.org/blog/chainlink-integrated-as-part-of-the-rif-gateways-ecosystem-on-rsk) this will be a powerful alternative as Chainlink oracles are widely used in Ethereum. 13 | -------------------------------------------------------------------------------- /audit/rLending_Security_Audit_Final_Report_v210303.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tropykus/protocol-rsk/8c3e32d01b880619cdb4820fad951b810a69b883/audit/rLending_Security_Audit_Final_Report_v210303.pdf -------------------------------------------------------------------------------- /calcSize.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const exec = require('child_process').execSync; 4 | const chalk = require('chalk'); 5 | const { table } = require('table'); 6 | 7 | async function readFiles() { 8 | if (fs.existsSync("./.build/contracts.json")) { 9 | return fs.readFileSync("./.build/contracts.json"); 10 | } else { 11 | console.log('Compile the contracts before running this script'); 12 | process.exit() 13 | } 14 | } 15 | 16 | async function printSize() { 17 | let file = await readFiles(); 18 | console.log(`NOTE- Maximum size of contracts allowed to deloyed on the Ethereum mainnet is 24 KB(EIP170)`); 19 | console.log(`---- Size of the contracts ----`); 20 | let dataTable = [['Contracts', 'Size in KB']]; 21 | let contracts = JSON.parse(file.toString()); 22 | for(let contract in contracts.contracts) { 23 | if(contract.indexOf('test') == -1) { 24 | let content = contracts.contracts[contract].bin; 25 | let sizeInKB = content.toString().length / 2 / 1024; 26 | if (sizeInKB > 24) 27 | dataTable.push([chalk.red(contract),chalk.red(sizeInKB)]); 28 | else if (sizeInKB > 20) 29 | dataTable.push([chalk.yellow(contract),chalk.yellow(sizeInKB)]); 30 | else 31 | dataTable.push([chalk.green(contract),chalk.green(sizeInKB)]); 32 | } 33 | } 34 | console.log(table(dataTable)); 35 | } 36 | 37 | printSize(); -------------------------------------------------------------------------------- /contracts/CCompLikeDelegate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./CErc20Delegate.sol"; 4 | 5 | interface CompLike { 6 | function delegate(address delegatee) external; 7 | } 8 | 9 | /** 10 | * @title Compound's CCompLikeDelegate Contract 11 | * @notice CTokens which can 'delegate votes' of their underlying ERC-20 12 | * @author tropykus 13 | */ 14 | contract CCompLikeDelegate is CErc20Delegate { 15 | /** 16 | * @notice Construct an empty delegate 17 | */ 18 | constructor() public CErc20Delegate() {} 19 | 20 | /** 21 | * @notice Admin call to delegate the votes of the COMP-like underlying 22 | * @param compLikeDelegatee The address to delegate votes to 23 | */ 24 | function _delegateCompLikeTo(address compLikeDelegatee) external { 25 | require(msg.sender == admin, "E7"); 26 | CompLike(underlying).delegate(compLikeDelegatee); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/CErc20Delegate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./CErc20.sol"; 4 | 5 | /** 6 | * @title tropykus CErc20Delegate Contract 7 | * @notice CTokens which wrap an EIP-20 underlying and are delegated to 8 | * @author tropykus 9 | */ 10 | contract CErc20Delegate is CErc20, CDelegateInterface { 11 | /** 12 | * @notice Construct an empty delegate 13 | */ 14 | constructor() public { 15 | // solium-disable-previous-line no-empty-blocks 16 | } 17 | 18 | /** 19 | * @notice Called by the delegator on a delegate to initialize it for duty 20 | * @param data The encoded bytes data for any initialization 21 | */ 22 | function _becomeImplementation(bytes memory data) public { 23 | // Shh -- currently unused 24 | data; 25 | 26 | // Shh -- we don't ever want this hook to be marked pure 27 | if (false) { 28 | implementation = address(0); 29 | } 30 | 31 | require(msg.sender == admin, "E5"); 32 | } 33 | 34 | /** 35 | * @notice Called by the delegator on a delegate to forfeit its responsibility 36 | */ 37 | function _resignImplementation() public { 38 | // Shh -- we don't ever want this hook to be marked pure 39 | if (false) { 40 | implementation = address(0); 41 | } 42 | 43 | require(msg.sender == admin, "E6"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/CErc20Immutable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./CErc20.sol"; 4 | 5 | /** 6 | * @title tropykus CErc20Immutable Contract 7 | * @notice CTokens which wrap an EIP-20 underlying and are immutable 8 | * @author tropykus 9 | */ 10 | contract CErc20Immutable is CErc20 { 11 | /** 12 | * @notice Construct a new money market 13 | * @param underlying_ The address of the underlying asset 14 | * @param comptroller_ The address of the Comptroller 15 | * @param interestRateModel_ The address of the interest rate model 16 | * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 17 | * @param name_ ERC-20 name of this token 18 | * @param symbol_ ERC-20 symbol of this token 19 | * @param decimals_ ERC-20 decimal precision of this token 20 | * @param admin_ Address of the administrator of this token 21 | */ 22 | constructor(address underlying_, 23 | ComptrollerInterface comptroller_, 24 | InterestRateModel interestRateModel_, 25 | uint initialExchangeRateMantissa_, 26 | string memory name_, 27 | string memory symbol_, 28 | uint8 decimals_, 29 | address payable admin_) public { 30 | // Creator of the contract is admin during initialization 31 | admin = msg.sender; 32 | 33 | // Initialize the market 34 | initialize(underlying_, comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, decimals_); 35 | 36 | // Set the proper admin now that initialization is done 37 | admin = admin_; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/CRBTCCompanionInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./CToken.sol"; 4 | 5 | interface CRBTCCompanionInterface { 6 | function verifySupplyPerAccountLimit( 7 | uint256 underlyingAmount, 8 | uint256 mintAmount 9 | ) external; 10 | 11 | function getTotalBorrowsInOtherMarkets() 12 | external 13 | view 14 | returns ( 15 | uint256, 16 | uint256, 17 | uint256 18 | ); 19 | 20 | function verifySupplyMarketCapLimit( 21 | uint256 totalSupply, 22 | uint256 mintAmount, 23 | uint256 exchangeRateMantissa 24 | ) external; 25 | } 26 | -------------------------------------------------------------------------------- /contracts/CRDOC.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./CErc20.sol"; 4 | 5 | contract CRDOC is CErc20 { 6 | /** 7 | * @notice Construct a new money market 8 | * @param underlying_ The address of the underlying asset 9 | * @param comptroller_ The address of the Comptroller 10 | * @param interestRateModel_ The address of the interest rate model 11 | * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 12 | * @param name_ ERC-20 name of this token 13 | * @param symbol_ ERC-20 symbol of this token 14 | * @param decimals_ ERC-20 decimal precision of this token 15 | * @param admin_ Address of the administrator of this token 16 | */ 17 | constructor(address underlying_, 18 | ComptrollerInterface comptroller_, 19 | InterestRateModel interestRateModel_, 20 | uint initialExchangeRateMantissa_, 21 | string memory name_, 22 | string memory symbol_, 23 | uint8 decimals_, 24 | address payable admin_) public { 25 | // Creator of the contract is admin during initialization 26 | admin = msg.sender; 27 | 28 | // Initialize the market 29 | initialize(underlying_, comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, decimals_); 30 | 31 | // Set the proper admin now that initialization is done 32 | admin = admin_; 33 | } 34 | 35 | function borrowInternalValidations( 36 | address borrower, 37 | BorrowLocalVars memory vars 38 | ) internal returns (BorrowLocalVars memory) { 39 | borrower; 40 | require(vars.accountBorrowsNew <= 1000e18, "RD1"); 41 | return vars; 42 | } 43 | } -------------------------------------------------------------------------------- /contracts/CarefulMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | /** 4 | * @title Careful Math 5 | * @author tropykus 6 | * @notice Derived from OpenZeppelin's SafeMath library 7 | * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol 8 | */ 9 | contract CarefulMath { 10 | 11 | /** 12 | * @dev Possible error codes that we can return 13 | */ 14 | enum MathError { 15 | NO_ERROR, 16 | DIVISION_BY_ZERO, 17 | INTEGER_OVERFLOW, 18 | INTEGER_UNDERFLOW 19 | } 20 | 21 | /** 22 | * @dev Multiplies two numbers, returns an error on overflow. 23 | */ 24 | function mulUInt(uint a, uint b) internal pure returns (MathError, uint) { 25 | if (a == 0) { 26 | return (MathError.NO_ERROR, 0); 27 | } 28 | 29 | uint c = a * b; 30 | 31 | if (c / a != b) { 32 | return (MathError.INTEGER_OVERFLOW, 0); 33 | } else { 34 | return (MathError.NO_ERROR, c); 35 | } 36 | } 37 | 38 | /** 39 | * @dev Integer division of two numbers, truncating the quotient. 40 | */ 41 | function divUInt(uint a, uint b) internal pure returns (MathError, uint) { 42 | if (b == 0) { 43 | return (MathError.DIVISION_BY_ZERO, 0); 44 | } 45 | 46 | return (MathError.NO_ERROR, a / b); 47 | } 48 | 49 | /** 50 | * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend). 51 | */ 52 | function subUInt(uint a, uint b) internal pure returns (MathError, uint) { 53 | if (b <= a) { 54 | return (MathError.NO_ERROR, a - b); 55 | } else { 56 | return (MathError.INTEGER_UNDERFLOW, 0); 57 | } 58 | } 59 | 60 | /** 61 | * @dev Adds two numbers, returns an error on overflow. 62 | */ 63 | function addUInt(uint a, uint b) internal pure returns (MathError, uint) { 64 | uint c = a + b; 65 | 66 | if (c >= a) { 67 | return (MathError.NO_ERROR, c); 68 | } else { 69 | return (MathError.INTEGER_OVERFLOW, 0); 70 | } 71 | } 72 | 73 | /** 74 | * @dev add a and b and then subtract c 75 | */ 76 | function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) { 77 | (MathError err0, uint sum) = addUInt(a, b); 78 | 79 | if (err0 != MathError.NO_ERROR) { 80 | return (err0, 0); 81 | } 82 | 83 | return subUInt(sum, c); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/JumpRateModelV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./BaseJumpRateModelV2.sol"; 4 | 5 | /** 6 | * @title tropykus JumpRateModel Contract V2 for V2 cTokens 7 | * @author tropykus 8 | * @notice Supports only for V2 cTokens 9 | */ 10 | contract JumpRateModelV2 is BaseJumpRateModelV2 { 11 | constructor( 12 | uint256 baseRatePerYear, 13 | uint256 multiplierPerYear, 14 | uint256 jumpMultiplierPerYear, 15 | uint256 kink_, 16 | address owner_ 17 | ) 18 | public 19 | BaseJumpRateModelV2( 20 | baseRatePerYear, 21 | multiplierPerYear, 22 | jumpMultiplierPerYear, 23 | kink_, 24 | owner_ 25 | ) 26 | {} 27 | } 28 | -------------------------------------------------------------------------------- /contracts/LegacyInterestRateModel.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | /** 4 | * @title tropykus Legacy InterestRateModel Interface 5 | * @author tropykus (modified by Arr00) 6 | */ 7 | contract LegacyInterestRateModel { 8 | /// @notice Indicator that this is an InterestRateModel contract (for inspection) 9 | bool public constant isInterestRateModel = true; 10 | 11 | /** 12 | * @notice Calculates the current supply interest rate per block 13 | * @param cash The total amount of cash the market has 14 | * @param borrows The total amount of borrows the market has outstanding 15 | * @param reserves The total amount of reserves the market has 16 | * @param reserveFactorMantissa The current reserve factor the market has 17 | * @return The supply rate per block (as a percentage, and scaled by 1e18) 18 | */ 19 | function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) external view returns (uint); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /contracts/LegacyJumpRateModelV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./BaseJumpRateModelV2.sol"; 4 | import "./LegacyInterestRateModel.sol"; 5 | 6 | 7 | /** 8 | * @title tropykus JumpRateModel Contract V2 for legacy cTokens 9 | * @author tropykus 10 | * @notice Supports only legacy cTokens 11 | */ 12 | contract LegacyJumpRateModelV2 is LegacyInterestRateModel, BaseJumpRateModelV2 { 13 | constructor(uint baseRatePerYear, uint multiplierPerYear, uint jumpMultiplierPerYear, uint kink_, address owner_) 14 | BaseJumpRateModelV2(baseRatePerYear,multiplierPerYear,jumpMultiplierPerYear,kink_,owner_) public {} 15 | } 16 | -------------------------------------------------------------------------------- /contracts/Maximillion.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./CRBTC.sol"; 4 | 5 | /** 6 | * @title tropykus Maximillion Contract 7 | * @author tropykus 8 | */ 9 | contract Maximillion { 10 | /** 11 | * @notice The default cRBTC market to repay in 12 | */ 13 | CRBTC public cRBTC; 14 | 15 | /** 16 | * @notice Construct a Maximillion to repay max in a CRBTC market 17 | */ 18 | constructor(CRBTC cRBTC_) public { 19 | cRBTC = cRBTC_; 20 | } 21 | 22 | /** 23 | * @notice msg.sender sends Ether to repay an account's borrow in the cRBTC market 24 | * @dev The provided Ether is applied towards the borrow balance, any excess is refunded 25 | * @param borrower The address of the borrower account to repay on behalf of 26 | */ 27 | function repayBehalf(address borrower) public payable { 28 | repayBehalfExplicit(borrower, cRBTC); 29 | } 30 | 31 | /** 32 | * @notice msg.sender sends Ether to repay an account's borrow in a cRBTC market 33 | * @dev The provided Ether is applied towards the borrow balance, any excess is refunded 34 | * @param borrower The address of the borrower account to repay on behalf of 35 | * @param cRBTC_ The address of the cRBTC contract to repay in 36 | */ 37 | function repayBehalfExplicit(address borrower, CRBTC cRBTC_) public payable { 38 | uint received = msg.value; 39 | uint borrows = cRBTC_.borrowBalanceCurrent(borrower); 40 | if (received > borrows) { 41 | cRBTC_.repayBorrowBehalf.value(borrows)(borrower); 42 | msg.sender.transfer(received - borrows); 43 | } else { 44 | cRBTC_.repayBorrowBehalf.value(received)(borrower); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/PriceOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./CToken.sol"; 4 | 5 | contract PriceOracle { 6 | /// @notice Indicator that this is a PriceOracle contract (for inspection) 7 | bool public constant isPriceOracle = true; 8 | 9 | /** 10 | * @notice Get the underlying price of a cToken asset 11 | * @param cToken The cToken to get the underlying price of 12 | * @return The underlying asset price mantissa (scaled by 1e18). 13 | * Zero means the price is unavailable. 14 | */ 15 | function getUnderlyingPrice(CToken cToken) external view returns (uint); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/PriceOracleAdapter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | contract PriceOracleAdapter { 4 | /// @notice Event adapter interface updated 5 | event PriceOracleAdapterUpdated(address oldAddress, address newAddress); 6 | 7 | /** 8 | * @notice Get the price 9 | * @return The underlying asset price mantissa (scaled by 1e18). 10 | * Zero means the price is unavailable. 11 | */ 12 | function assetPrices(address cTokenAddress) external view returns (uint256); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/SignedSafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.5.16; 3 | 4 | /** 5 | * @dev Wrappers over Solidity's arithmetic operations. 6 | * 7 | * NOTE: `SignedSafeMath` is no longer needed starting with Solidity 0.8. The compiler 8 | * now has built in overflow checking. 9 | */ 10 | library SignedSafeMath { 11 | /** 12 | * @dev Returns the multiplication of two signed integers, reverting on 13 | * overflow. 14 | * 15 | * Counterpart to Solidity's `*` operator. 16 | * 17 | * Requirements: 18 | * 19 | * - Multiplication cannot overflow. 20 | */ 21 | function mul(int256 a, int256 b) internal pure returns (int256) { 22 | return a * b; 23 | } 24 | 25 | /** 26 | * @dev Returns the integer division of two signed integers. Reverts on 27 | * division by zero. The result is rounded towards zero. 28 | * 29 | * Counterpart to Solidity's `/` operator. 30 | * 31 | * Requirements: 32 | * 33 | * - The divisor cannot be zero. 34 | */ 35 | function div(int256 a, int256 b) internal pure returns (int256) { 36 | return a / b; 37 | } 38 | 39 | /** 40 | * @dev Returns the subtraction of two signed integers, reverting on 41 | * overflow. 42 | * 43 | * Counterpart to Solidity's `-` operator. 44 | * 45 | * Requirements: 46 | * 47 | * - Subtraction cannot overflow. 48 | */ 49 | function sub(int256 a, int256 b) internal pure returns (int256) { 50 | return a - b; 51 | } 52 | 53 | /** 54 | * @dev Returns the addition of two signed integers, reverting on 55 | * overflow. 56 | * 57 | * Counterpart to Solidity's `+` operator. 58 | * 59 | * Requirements: 60 | * 61 | * - Addition cannot overflow. 62 | */ 63 | function add(int256 a, int256 b) internal pure returns (int256) { 64 | return a + b; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/mocks/MockPriceProviderMoC.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../CErc20.sol"; 4 | 5 | /** 6 | * @title A mock price provider of Money on Chain (MoC) 7 | * @notice You can use this contract for only simulation 8 | */ 9 | contract MockPriceProviderMoC { 10 | /// @notice rbtcPrice of the interface provicer MoC 11 | bytes32 rbtcPrice; 12 | /// @notice has of the interface provicer MoC 13 | bool has; 14 | /// @notice Address of the guardian 15 | address public guardian; 16 | /// @notice Event rbtcPrice updated 17 | event MockPriceProviderMoCUpdated(uint256 oldPrice, uint256 newPrice); 18 | 19 | constructor(address guardian_, uint256 price) public { 20 | require( 21 | guardian_ != address(0), 22 | "MockPriceProviderMoC: address could not be 0" 23 | ); 24 | require( 25 | price != uint256(0), 26 | "MockPriceProviderMoC: price could not be 0" 27 | ); 28 | guardian = guardian_; 29 | rbtcPrice = bytes32(price); 30 | has = true; 31 | } 32 | 33 | function peek() public view returns (bytes32, bool) { 34 | return (rbtcPrice, has); 35 | } 36 | 37 | /** 38 | * @notice Set the rbtcPrice price provider 39 | * @param price uint of price provider 40 | */ 41 | function setPrice(uint256 price) public { 42 | require( 43 | msg.sender == guardian, 44 | "MockPriceProviderMoC: only guardian may set the address" 45 | ); 46 | require( 47 | price != uint256(0), 48 | "MockPriceProviderMoC: price could not be 0" 49 | ); 50 | //set old price 51 | bytes32 oldRbtcPrice = rbtcPrice; 52 | //update rbtcPrice 53 | rbtcPrice = bytes32(price); 54 | //emit event 55 | emit MockPriceProviderMoCUpdated(uint256(oldRbtcPrice), uint256(rbtcPrice)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/mocks/SimplePriceOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../PriceOracle.sol"; 4 | import "../CErc20.sol"; 5 | 6 | /** 7 | * @title Simplified Oracle for testing purposes. 8 | * @author tropykus 9 | * @notice This contract is meant for testing only. 10 | */ 11 | contract SimplePriceOracle is PriceOracle { 12 | mapping(address => uint) prices; 13 | event PricePosted(address asset, uint previousPriceMantissa, uint requestedPriceMantissa, uint newPriceMantissa); 14 | 15 | function getUnderlyingPrice(CToken cToken) public view returns (uint) { 16 | if (compareStrings(cToken.symbol(), "cRBTC")) { 17 | return prices[(address(cToken))]; 18 | } else { 19 | return prices[address(CErc20(address(cToken)).underlying())]; 20 | } 21 | } 22 | 23 | function setUnderlyingPrice(CToken cToken, uint underlyingPriceMantissa) public { 24 | address asset = address(CErc20(address(cToken)).underlying()); 25 | emit PricePosted(asset, prices[asset], underlyingPriceMantissa, underlyingPriceMantissa); 26 | prices[asset] = underlyingPriceMantissa; 27 | } 28 | 29 | function setDirectPrice(address asset, uint price) public { 30 | emit PricePosted(asset, prices[asset], price, price); 31 | prices[asset] = price; 32 | } 33 | 34 | // v1 price oracle interface for use as backing of proxy 35 | function assetPrices(address asset) external view returns (uint) { 36 | return prices[asset]; 37 | } 38 | 39 | function compareStrings(string memory a, string memory b) internal pure returns (bool) { 40 | return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /hardhat.networks.js: -------------------------------------------------------------------------------- 1 | const networks = { 2 | hardhat: { 3 | blockGasLimit: 200000000, 4 | defaultBalanceEther: 20000000000, 5 | gas: 6800000, 6 | allowUnlimitedContractSize: true, 7 | chainId: 1337 8 | }, 9 | ganache: { 10 | url: 'http://127.0.0.1:8545', 11 | blockGasLimit: 200000000, 12 | allowUnlimitedContractSize: false, 13 | chainId: 1337 14 | }, 15 | rskregtest: { 16 | url: 'http://127.0.0.1:4444', 17 | blockGasLimit: 68000000, 18 | allowUnlimitedContractSize: false, 19 | chainId: 33 20 | } 21 | } 22 | 23 | if (process.env.HDWALLET_MNEMONIC) { 24 | networks.rsktestnet = { 25 | url: 'https://public-node.testnet.rsk.co', 26 | blockGasLimit: 6800000, 27 | gas: 6800000, 28 | allowUnlimitedContractSize: false, 29 | chainId: 31, 30 | timeout: 300000, 31 | accounts: { 32 | mnemonic: process.env.HDWALLET_MNEMONIC 33 | } 34 | } 35 | networks.rskmainnet1 = { 36 | url: 'https://rsknode-3.tropykus.com/rsk', 37 | blockGasLimit: 6800000, 38 | gas: 6800000, 39 | allowUnlimitedContractSize: false, 40 | chainId: 30, 41 | accounts: { 42 | mnemonic: process.env.HDWALLET_MNEMONIC 43 | } 44 | } 45 | networks.rskmainnet2 = { 46 | url: 'https://rsknode-2.tropykus.finance/rsk', 47 | blockGasLimit: 6800000, 48 | gas: 6800000, 49 | allowUnlimitedContractSize: false, 50 | chainId: 30, 51 | accounts: { 52 | mnemonic: process.env.HDWALLET_MNEMONIC 53 | } 54 | } 55 | } else { 56 | console.warn('No hdwallet available for testnet and mainnet') 57 | } 58 | 59 | module.exports = networks -------------------------------------------------------------------------------- /instructions: -------------------------------------------------------------------------------- 1 | load = async () => { 2 | [dep, alice, bob, eve] = await ethers.getSigners(); 3 | comptroller = await ethers.getContractAt('ComptrollerG6', Comptroller, dep); 4 | rif = await ethers.getContractAt('StandardToken', RIF, dep); 5 | doc = await ethers.getContractAt('StandardToken', DOC, dep); 6 | rdoc = await ethers.getContractAt('StandardToken', RDOC, dep); 7 | usdt = await ethers.getContractAt('StandardToken', USDT, dep); 8 | crif = await ethers.getContractAt('CErc20Immutable', cRIF, dep); 9 | cdoc = await ethers.getContractAt('CErc20Immutable', cDOC, dep); 10 | crdoc = await ethers.getContractAt('CRDOC', cRDOC, dep); 11 | cusdt = await ethers.getContractAt('CErc20Immutable', cUSDT, dep); 12 | crbtc = await ethers.getContractAt('CRBTC', cRBTC, dep); 13 | csat = await ethers.getContractAt('CRBTC', cSAT, dep); 14 | csatCompanion = await ethers.getContractAt('CRBTCCompanion', cRBTCCompanion, dep); 15 | priceOracleProxy = await ethers.getContractAt('PriceOracleProxy', PriceOracleProxy, dep); 16 | rbtcOracle = await ethers.getContractAt('MockPriceProviderMoC', RBTCOracle, dep); 17 | mkts = await comptroller.getAllMarkets(); 18 | users = [dep, alice, bob, eve]; 19 | } 20 | 21 | load(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tropykus-protocol", 3 | "version": "0.2.4", 4 | "description": "The tropykus Money Market", 5 | "main": "index.js", 6 | "scripts": { 7 | "compile": "./script/compile", 8 | "console": "if node -v | grep -E \"v(12|13)\" > /dev/null; then flags=\"-n --experimental-repl-await\"; fi; npx $flags saddle console", 9 | "coverage": "./script/coverage", 10 | "deploy": "npx hardhat deploy --write true --network", 11 | "flatten": "npx waffle flatten", 12 | "lint": "./script/lint", 13 | "repl": "./scenario/script/repl", 14 | "profile": "yarn test tests/gasProfiler.js", 15 | "report": "npx run ./script/saddle/gasReport.js", 16 | "test": "./script/test", 17 | "rsk": "./script/rsk", 18 | "abis": "rm -rf abis && mkdir abis && node datagen.js && yes | cp -rf abis ../app/src", 19 | "test:prepare": "NO_RUN=true ./script/test", 20 | "checksize": "npx run ./script/saddle/comptrollerSize.js" 21 | }, 22 | "repository": "git@github.com:TruStartUp/tropykus-protocol.git", 23 | "author": "TRU", 24 | "license": "UNLICENSED", 25 | "devDependencies": { 26 | "@nomiclabs/hardhat-ethers": "^2.0.2", 27 | "@nomiclabs/hardhat-waffle": "^2.0.1", 28 | "bignumber.js": "^9.0.1", 29 | "chai": "^4.3.4", 30 | "ethereum-waffle": "^3.4.0", 31 | "ethers": "^5.5.1", 32 | "hardhat": "^2.6.7", 33 | "hardhat-contract-sizer": "^2.1.1", 34 | "hardhat-deploy": "^0.9.4", 35 | "jest-diff": "^26.4.2", 36 | "jest-junit": "^11.1.0", 37 | "solium": "^1.2.5", 38 | "solparse": "^2.2.8" 39 | }, 40 | "dependencies": { 41 | "eth-saddle": "^0.1.21" 42 | }, 43 | "resolutions": { 44 | "scrypt.js": "https://registry.npmjs.org/@compound-finance/ethereumjs-wallet/-/ethereumjs-wallet-0.6.3.tgz", 45 | "**/ganache-core": "https://github.com/compound-finance/ganache-core.git#jflatow/unbreak-fork" 46 | }, 47 | "engines": { 48 | "node": "=12.X" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /reporterConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "spec, mocha-junit-reporter" 3 | } -------------------------------------------------------------------------------- /scenario/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound-protocol-alpha", 3 | "version": "0.2.1", 4 | "description": "The Compound Money Market", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "./script/webpack" 8 | }, 9 | "repository": "git@github.com:compound-finance/money-market.git", 10 | "author": "Compound Finance", 11 | "license": "UNLICENSED", 12 | "devDependencies": { 13 | "request": "^2.88.2", 14 | "solparse": "^2.2.8", 15 | "ts-loader": "^8.0.3", 16 | "ts-pegjs": "^0.2.7", 17 | "typescript": "^4.0.2", 18 | "webpack": "^4.44.1", 19 | "webpack-bundle-analyzer": "^3.8.0", 20 | "webpack-cli": "^3.3.12" 21 | }, 22 | "dependencies": { 23 | "bignumber.js": "9.0.0", 24 | "eth-saddle": "^0.1.21", 25 | "ethers": "^5.0.8", 26 | "immutable": "^4.0.0-rc.12", 27 | "truffle-flattener": "^1.4.4", 28 | "web3": "^1.2.11" 29 | }, 30 | "resolutions": { 31 | "scrypt.js": "https://registry.npmjs.org/@compound-finance/ethereumjs-wallet/-/ethereumjs-wallet-0.6.3.tgz", 32 | "**/ganache-core": "https://github.com/compound-finance/ganache-core.git#jflatow/unbreak-fork" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /scenario/script/generate_parser: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | scenario_dir="$(cd $dir/.. && pwd)" 7 | 8 | "$scenario_dir/node_modules/.bin/pegjs" \ 9 | --plugin "$scenario_dir/node_modules/ts-pegjs" \ 10 | -o "$scenario_dir/src/Parser.ts" \ 11 | --cache \ 12 | --allowed-start-rules tests,step,macros \ 13 | "$scenario_dir/Grammar.pegjs" 14 | -------------------------------------------------------------------------------- /scenario/script/repl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | dir=`dirname $0` 6 | tsc_root="$dir/.." 7 | proj_root="$dir/../.." 8 | networks_root="$dir/../../networks" 9 | network=${NETWORK:-development} 10 | script=() 11 | verbose="$VERBOSE" 12 | dry_run="$DRY_RUN" 13 | no_tsc="$NO_TSC" 14 | 15 | [ -n "$SCRIPT" ] && script+=("$SCRIPT") 16 | 17 | usage() { echo "$0 usage:" && grep ".)\ #" $0; exit 0; } 18 | while getopts ":hdn:e:s:vt" arg; do 19 | case $arg in 20 | c) # Don't compile 21 | no_compile="true" 22 | ;; 23 | d) # Dry run 24 | dry_run="true" 25 | ;; 26 | h) # Hypothetical 27 | hypothetical="true" 28 | ;; 29 | e) # Add variables for script (key=value,key2=value2) 30 | env_vars="$OPTARG" 31 | ;; 32 | n) # Specify network 33 | network=$OPTARG 34 | ;; 35 | s) # Specify a script to run 36 | [ ! -f "$OPTARG" ] \ 37 | && echo "Cannot find script $OPTARG" \ 38 | && exit 1 39 | script+=("$OPTARG") 40 | ;; 41 | t) # Don't build TSC 42 | no_tsc="true" 43 | ;; 44 | 45 | v) # Verbose 46 | verbose="true" 47 | ;; 48 | 49 | h | *) # Display help. 50 | usage 51 | exit 0 52 | ;; 53 | esac 54 | done 55 | 56 | [[ -z $no_tsc ]] && "$dir/tsc" 57 | [[ -z $no_compile ]] && "$proj_root/script/compile" 58 | 59 | proj_root="$proj_root" env_vars="$env_vars" dry_run="$dry_run" script="$(IFS=, ; echo "${script[*]}")" network="$network" verbose="$verbose" node "$tsc_root/.tsbuilt/Repl.js" 60 | -------------------------------------------------------------------------------- /scenario/script/tsc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | dir=`dirname $0` 6 | scenario_dir="$(cd $dir/.. && pwd)" 7 | parent_dir="$(cd $dir/../.. && pwd)" 8 | cache_file="$scenario_dir/.tscache" 9 | checksum="$(echo $scenario_dir/src/{*,**/}*.* | xargs shasum| shasum| cut -d' ' -f 1)" 10 | 11 | if [ ! -d "$scenario_dir/node_modules" ]; then 12 | echo "Getting scenario packages..." 13 | cd "$scenario_dir" && yarn 14 | fi 15 | 16 | trap cleanup EXIT 17 | 18 | cleanup() { 19 | mv "$parent_dir/node_modules_tmp" "$parent_dir/node_modules" 20 | } 21 | 22 | mv "$parent_dir/node_modules" "$parent_dir/node_modules_tmp" 23 | 24 | if [ -z "$rebuild" -a -f "$cache_file" ]; then 25 | cached="$(cat $cache_file)" 26 | 27 | if [ "$cached" == "$checksum" ]; then 28 | echo "Skipping Scenario Rebuild (set rebuild=true to force)" 29 | exit 0 30 | fi 31 | fi 32 | 33 | echo "Building Scenario Runner..." 34 | cd "$scenario_dir" && node "$scenario_dir/node_modules/.bin/tsc" ${TSC_ARGS-"--skipLibCheck"} 35 | 36 | echo "$checksum" > "$cache_file" 37 | -------------------------------------------------------------------------------- /scenario/script/webpack: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | scenario_dir="$(cd $dir/.. && pwd)" 7 | 8 | cd "$scenario_dir" && 9 | mkdir -p ./build && 10 | ./node_modules/.bin/webpack \ 11 | --mode production \ 12 | --config ./webpack.config.js \ 13 | --entry ./src/Web.ts \ 14 | --module-bind 'ts=ts-loader' \ 15 | --module-bind 'exports-loader?parse' \ 16 | --resolve-extensions ".ts,.js" \ 17 | --output-library-target window \ 18 | --output ./build/scenario.js 19 | -------------------------------------------------------------------------------- /scenario/src/Accounts.ts: -------------------------------------------------------------------------------- 1 | import {World} from './World'; 2 | import {Map} from 'immutable'; 3 | 4 | export const accountMap = { 5 | "default": 0, 6 | "root": 0, 7 | "admin": 0, 8 | "first": 0, 9 | 10 | "bank": 1, 11 | "second": 1, 12 | 13 | "geoff": 2, 14 | "third": 2, 15 | "guardian": 2, 16 | 17 | "torrey": 3, 18 | "fourth": 3, 19 | 20 | "robert": 4, 21 | "fifth": 4, 22 | 23 | "coburn": 5, 24 | "sixth": 5, 25 | 26 | "jared": 6, 27 | "seventh": 6 28 | }; 29 | 30 | export interface Account { 31 | name: string 32 | address: string 33 | } 34 | 35 | export type Accounts = Map 36 | 37 | export function accountAliases(index: number): string[] { 38 | return Object.entries(accountMap).filter(([k,v]) => v === index).map(([k,v]) => k); 39 | } 40 | 41 | export function loadAccounts(accounts: string[]): Accounts { 42 | return Object.entries(accountMap).reduce((acc, [name, index]) => { 43 | if (accounts[index]) { 44 | return acc.set(name, { name: name, address: accounts[index] }); 45 | } else { 46 | return acc; 47 | } 48 | }, >Map({})); 49 | } 50 | -------------------------------------------------------------------------------- /scenario/src/Action.ts: -------------------------------------------------------------------------------- 1 | import {Invokation} from './Invokation'; 2 | 3 | export class Action { 4 | log: string; 5 | invokation: Invokation; 6 | 7 | constructor(log: string, invokation: Invokation) { 8 | this.log = log; 9 | this.invokation = invokation; 10 | } 11 | 12 | toString() { 13 | return `Action: log=${this.log}, result=${this.invokation.toString()}`; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scenario/src/Assert.ts: -------------------------------------------------------------------------------- 1 | export type Expect = (actual: any) => { 2 | toEqual: (expected: any) => any 3 | fail: (message: string) => any 4 | } 5 | 6 | export const throwExpect: Expect = (x) => { 7 | return { 8 | toEqual: (y) => { 9 | let xEnc = JSON.stringify(x); 10 | let yEnc = JSON.stringify(y); 11 | if (xEnc !== yEnc) { 12 | throw new Error(`expected ${x} to equal ${y}`); 13 | } 14 | }, 15 | fail: (reason) => { 16 | throw new Error(reason) 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /scenario/src/Builder/MaximillionBuilder.ts: -------------------------------------------------------------------------------- 1 | import {Event} from '../Event'; 2 | import {addAction, World} from '../World'; 3 | import {Maximillion} from '../Contract/Maximillion'; 4 | import {Invokation} from '../Invokation'; 5 | import {Arg, Fetcher, getFetcherValue} from '../Command'; 6 | import {storeAndSaveContract} from '../Networks'; 7 | import {getContract} from '../Contract'; 8 | import {getAddressV} from '../CoreValue'; 9 | import {AddressV} from '../Value'; 10 | 11 | const MaximillionContract = getContract("Maximillion"); 12 | 13 | export interface MaximillionData { 14 | invokation: Invokation, 15 | description: string, 16 | cRBTCAddress: string, 17 | address?: string 18 | } 19 | 20 | export async function buildMaximillion(world: World, from: string, event: Event): Promise<{world: World, maximillion: Maximillion, maximillionData: MaximillionData}> { 21 | const fetchers = [ 22 | new Fetcher<{cRBTC: AddressV}, MaximillionData>(` 23 | #### Maximillion 24 | 25 | * "" - Maximum Eth Repays Contract 26 | * E.g. "Maximillion Deploy" 27 | `, 28 | "Maximillion", 29 | [ 30 | new Arg("cRBTC", getAddressV) 31 | ], 32 | async (world, {cRBTC}) => { 33 | return { 34 | invokation: await MaximillionContract.deploy(world, from, [cRBTC.val]), 35 | description: "Maximillion", 36 | cRBTCAddress: cRBTC.val 37 | }; 38 | }, 39 | {catchall: true} 40 | ) 41 | ]; 42 | 43 | let maximillionData = await getFetcherValue("DeployMaximillion", fetchers, world, event); 44 | let invokation = maximillionData.invokation; 45 | delete maximillionData.invokation; 46 | 47 | if (invokation.error) { 48 | throw invokation.error; 49 | } 50 | const maximillion = invokation.value!; 51 | maximillionData.address = maximillion._address; 52 | 53 | world = await storeAndSaveContract( 54 | world, 55 | maximillion, 56 | 'Maximillion', 57 | invokation, 58 | [ 59 | { index: ['Maximillion'], data: maximillionData } 60 | ] 61 | ); 62 | 63 | return {world, maximillion, maximillionData}; 64 | } 65 | -------------------------------------------------------------------------------- /scenario/src/Builder/UnitrollerBuilder.ts: -------------------------------------------------------------------------------- 1 | import {Event} from '../Event'; 2 | import {addAction, World} from '../World'; 3 | import {Unitroller} from '../Contract/Unitroller'; 4 | import {Invokation} from '../Invokation'; 5 | import {Arg, Fetcher, getFetcherValue} from '../Command'; 6 | import {storeAndSaveContract} from '../Networks'; 7 | import {getContract} from '../Contract'; 8 | 9 | const UnitrollerContract = getContract("Unitroller"); 10 | 11 | export interface UnitrollerData { 12 | invokation: Invokation, 13 | description: string, 14 | address?: string 15 | } 16 | 17 | export async function buildUnitroller(world: World, from: string, event: Event): Promise<{world: World, unitroller: Unitroller, unitrollerData: UnitrollerData}> { 18 | const fetchers = [ 19 | new Fetcher<{}, UnitrollerData>(` 20 | #### Unitroller 21 | 22 | * "" - The Upgradable Comptroller 23 | * E.g. "Unitroller Deploy" 24 | `, 25 | "Unitroller", 26 | [], 27 | async (world, {}) => { 28 | return { 29 | invokation: await UnitrollerContract.deploy(world, from, []), 30 | description: "Unitroller" 31 | }; 32 | }, 33 | {catchall: true} 34 | ) 35 | ]; 36 | 37 | let unitrollerData = await getFetcherValue("DeployUnitroller", fetchers, world, event); 38 | let invokation = unitrollerData.invokation; 39 | delete unitrollerData.invokation; 40 | 41 | if (invokation.error) { 42 | throw invokation.error; 43 | } 44 | const unitroller = invokation.value!; 45 | unitrollerData.address = unitroller._address; 46 | 47 | world = await storeAndSaveContract( 48 | world, 49 | unitroller, 50 | 'Unitroller', 51 | invokation, 52 | [ 53 | { index: ['Unitroller'], data: unitrollerData } 54 | ] 55 | ); 56 | 57 | return {world, unitroller, unitrollerData}; 58 | } 59 | -------------------------------------------------------------------------------- /scenario/src/Completer.ts: -------------------------------------------------------------------------------- 1 | import {World} from './World'; 2 | import {Macros} from './Macro'; 3 | 4 | // TODO: Get smarter about storing actions as data 5 | const actions: string[] = [ 6 | "Read", 7 | "Assert", 8 | "FastForward", 9 | "Inspect", 10 | "Debug", 11 | "From", 12 | "Invariant", 13 | "Comptroller", 14 | "cToken", 15 | "Erc20", 16 | ]; 17 | 18 | function caseInsensitiveSort(a: string, b: string): number { 19 | let A = a.toUpperCase(); 20 | let B = b.toUpperCase(); 21 | 22 | if (A < B) { 23 | return -1; 24 | } else if (A > B) { 25 | return 1; 26 | } else { 27 | return 0; 28 | } 29 | } 30 | 31 | export function complete(world: World, macros: Macros, line: string) { 32 | let allActions = actions.concat(Object.keys(macros)).sort(caseInsensitiveSort); 33 | const hits = allActions.filter((c) => c.toLowerCase().startsWith(line.toLowerCase())); 34 | 35 | return [hits, line]; 36 | } 37 | -------------------------------------------------------------------------------- /scenario/src/Contract/CErc20Delegate.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Sendable } from '../Invokation'; 3 | import { CTokenMethods, CTokenScenarioMethods } from './CToken'; 4 | 5 | interface CErc20DelegateMethods extends CTokenMethods { 6 | _becomeImplementation(data: string): Sendable; 7 | _resignImplementation(): Sendable; 8 | } 9 | 10 | interface CErc20DelegateScenarioMethods extends CTokenScenarioMethods { 11 | _becomeImplementation(data: string): Sendable; 12 | _resignImplementation(): Sendable; 13 | } 14 | 15 | export interface CErc20Delegate extends Contract { 16 | methods: CErc20DelegateMethods; 17 | name: string; 18 | } 19 | 20 | export interface CErc20DelegateScenario extends Contract { 21 | methods: CErc20DelegateScenarioMethods; 22 | name: string; 23 | } 24 | -------------------------------------------------------------------------------- /scenario/src/Contract/CErc20Delegator.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | import { CTokenMethods } from './CToken'; 4 | import { encodedNumber } from '../Encoding'; 5 | 6 | interface CErc20DelegatorMethods extends CTokenMethods { 7 | implementation(): Callable; 8 | _setImplementation( 9 | implementation_: string, 10 | allowResign: boolean, 11 | becomImplementationData: string 12 | ): Sendable; 13 | } 14 | 15 | interface CErc20DelegatorScenarioMethods extends CErc20DelegatorMethods { 16 | setTotalBorrows(amount: encodedNumber): Sendable; 17 | setTotalReserves(amount: encodedNumber): Sendable; 18 | } 19 | 20 | export interface CErc20Delegator extends Contract { 21 | methods: CErc20DelegatorMethods; 22 | name: string; 23 | } 24 | 25 | export interface CErc20DelegatorScenario extends Contract { 26 | methods: CErc20DelegatorMethods; 27 | name: string; 28 | } 29 | -------------------------------------------------------------------------------- /scenario/src/Contract/ComptrollerImpl.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | import { encodedNumber } from '../Encoding'; 4 | 5 | interface ComptrollerImplMethods { 6 | _become( 7 | comptroller: string, 8 | priceOracle?: string, 9 | maxAssets?: encodedNumber, 10 | closeFactor?: encodedNumber, 11 | reinitializing?: boolean 12 | ): Sendable; 13 | 14 | _become( 15 | comptroller: string, 16 | compRate: encodedNumber, 17 | compMarkets: string[], 18 | otherMarkets: string[] 19 | ): Sendable; 20 | } 21 | 22 | export interface ComptrollerImpl extends Contract { 23 | methods: ComptrollerImplMethods; 24 | } 25 | -------------------------------------------------------------------------------- /scenario/src/Contract/Counter.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { encodedNumber } from '../Encoding'; 3 | import { Callable, Sendable } from '../Invokation'; 4 | 5 | export interface CounterMethods { 6 | increment(by: encodedNumber): Sendable; 7 | } 8 | 9 | export interface Counter extends Contract { 10 | methods: CounterMethods; 11 | name: string; 12 | } 13 | -------------------------------------------------------------------------------- /scenario/src/Contract/Erc20.ts: -------------------------------------------------------------------------------- 1 | import {Contract} from '../Contract'; 2 | import {Callable, Sendable} from '../Invokation'; 3 | import {encodedNumber} from '../Encoding'; 4 | 5 | interface Erc20Methods { 6 | name(): Callable 7 | symbol(): Callable 8 | decimals(): Callable 9 | totalSupply(): Callable 10 | balanceOf(string): Callable 11 | allowance(owner: string, spender: string): Callable 12 | approve(address: string, amount: encodedNumber): Sendable 13 | allocateTo(address: string, amount: encodedNumber): Sendable 14 | transfer(address: string, amount: encodedNumber): Sendable 15 | transferFrom(owner: string, spender: string, amount: encodedNumber): Sendable 16 | setFail(fail: boolean): Sendable 17 | pause(): Sendable 18 | unpause(): Sendable 19 | setParams(newBasisPoints: encodedNumber, maxFee: encodedNumber): Sendable 20 | } 21 | 22 | export interface Erc20 extends Contract { 23 | methods: Erc20Methods 24 | name: string 25 | } 26 | -------------------------------------------------------------------------------- /scenario/src/Contract/Governor.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | import { encodedNumber } from '../Encoding'; 4 | 5 | export interface Proposal { 6 | id: number 7 | proposer: string 8 | eta: number 9 | targets: string[] 10 | values: number[] 11 | signatures: string[] 12 | calldatas: string[] 13 | startBlock: number 14 | endBlock: number 15 | forVotes: number 16 | againstVotes: number 17 | } 18 | 19 | export const proposalStateEnums = { 20 | 0: "Pending", 21 | 1: "Active", 22 | 2: "Canceled", 23 | 3: "Defeated", 24 | 4: "Succeeded", 25 | 5: "Queued", 26 | 6: "Expired", 27 | 7: "Executed" 28 | } 29 | 30 | export interface GovernorMethods { 31 | guardian(): Callable; 32 | propose(targets: string[], values: encodedNumber[], signatures: string[], calldatas: string[], description: string): Sendable 33 | proposals(proposalId: number): Callable; 34 | proposalCount(): Callable; 35 | latestProposalIds(proposer: string): Callable; 36 | getReceipt(proposalId: number, voter: string): Callable<{ hasVoted: boolean, support: boolean, votes: number }>; 37 | castVote(proposalId: number, support: boolean): Sendable; 38 | queue(proposalId: encodedNumber): Sendable; 39 | execute(proposalId: encodedNumber): Sendable; 40 | cancel(proposalId: encodedNumber): Sendable; 41 | setBlockNumber(blockNumber: encodedNumber): Sendable; 42 | setBlockTimestamp(blockTimestamp: encodedNumber): Sendable; 43 | state(proposalId: encodedNumber): Callable; 44 | __queueSetTimelockPendingAdmin(newPendingAdmin: string, eta: encodedNumber): Sendable; 45 | __executeSetTimelockPendingAdmin(newPendingAdmin: string, eta: encodedNumber): Sendable; 46 | __acceptAdmin(): Sendable; 47 | __abdicate(): Sendable; 48 | } 49 | 50 | export interface Governor extends Contract { 51 | methods: GovernorMethods; 52 | name: string; 53 | } 54 | -------------------------------------------------------------------------------- /scenario/src/Contract/InterestRateModel.ts: -------------------------------------------------------------------------------- 1 | import {Contract} from '../Contract'; 2 | import {Callable, Sendable} from '../Invokation'; 3 | import {encodedNumber} from '../Encoding'; 4 | 5 | interface InterestRateModelMethods { 6 | getBorrowRate(cash: encodedNumber, borrows: encodedNumber, reserves: encodedNumber): Callable 7 | } 8 | 9 | export interface InterestRateModel extends Contract { 10 | methods: InterestRateModelMethods 11 | name: string 12 | } 13 | -------------------------------------------------------------------------------- /scenario/src/Contract/Maximillion.ts: -------------------------------------------------------------------------------- 1 | import {Contract} from '../Contract'; 2 | import {Callable, Sendable} from '../Invokation'; 3 | 4 | interface MaximillionMethods { 5 | cRBTC(): Callable 6 | repayBehalf(string): Sendable 7 | } 8 | 9 | export interface Maximillion extends Contract { 10 | methods: MaximillionMethods 11 | } 12 | -------------------------------------------------------------------------------- /scenario/src/Contract/Pot.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | import { encodedNumber } from '../Encoding'; 4 | 5 | interface PotMethods { 6 | chi(): Callable; 7 | dsr(): Callable; 8 | rho(): Callable; 9 | pie(address: string): Callable; 10 | drip(): Sendable; 11 | file(what: string, data: encodedNumber): Sendable; 12 | join(amount: encodedNumber): Sendable; 13 | exit(amount: encodedNumber): Sendable; 14 | } 15 | 16 | export interface Pot extends Contract { 17 | methods: PotMethods; 18 | name: string; 19 | } 20 | -------------------------------------------------------------------------------- /scenario/src/Contract/PriceOracle.ts: -------------------------------------------------------------------------------- 1 | import {Contract} from '../Contract'; 2 | import {Callable, Sendable} from '../Invokation'; 3 | import {encodedNumber} from '../Encoding'; 4 | 5 | interface PriceOracleMethods { 6 | assetPrices(asset: string): Callable 7 | setUnderlyingPrice(cToken: string, amount: encodedNumber): Sendable 8 | setDirectPrice(address: string, amount: encodedNumber): Sendable 9 | 10 | // Anchor Price Oracle 11 | getPrice(asset: string): Callable 12 | readers(asset: string): Callable 13 | anchorAdmin(): Callable 14 | pendingAnchorAdmin(): Callable 15 | poster(): Callable 16 | maxSwing(): Callable 17 | anchors(asset: string): Callable<{0: number, 1: number}> 18 | pendingAnchors(asset: string): Callable 19 | _setPendingAnchor(asset: string, price: encodedNumber): Sendable 20 | _setPaused(paused: boolean): Sendable 21 | _setPendingAnchorAdmin(string): Sendable 22 | _acceptAnchorAdmin(): Sendable 23 | setPrice(asset: string, price: encodedNumber): Sendable 24 | setPrices(assets: string[], prices: encodedNumber[]): Sendable 25 | } 26 | 27 | export interface PriceOracle extends Contract { 28 | methods: PriceOracleMethods 29 | name: string 30 | } 31 | -------------------------------------------------------------------------------- /scenario/src/Contract/PriceOracleAdapterCompound.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | import { encodedNumber } from '../Encoding'; 4 | 5 | interface PriceOracleAdapterCompoundethodsMethods { 6 | setPriceProvider(priceProviderAddress: string): Sendable 7 | setKeyOracle(cTokenAddress: string, keyOracle: string): Sendable 8 | } 9 | 10 | export interface PriceOracleAdapterCompound extends Contract { 11 | methods: PriceOracleAdapterCompoundethodsMethods 12 | } 13 | -------------------------------------------------------------------------------- /scenario/src/Contract/PriceOracleProxy.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | import { encodedNumber } from '../Encoding'; 4 | 5 | interface PriceOracleProxyMethods { 6 | getUnderlyingPrice(asset: string): Callable 7 | v1PriceOracle(): Callable; 8 | setSaiPrice(amount: encodedNumber): Sendable 9 | setAdapterToToken(cTokenAddress: string, adapterAddress: string): Sendable 10 | setMockAdapter(addressAdapter: string): Sendable 11 | } 12 | 13 | export interface PriceOracleProxy extends Contract { 14 | methods: PriceOracleProxyMethods 15 | } -------------------------------------------------------------------------------- /scenario/src/Contract/Reservoir.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { encodedNumber } from '../Encoding'; 3 | import { Callable, Sendable } from '../Invokation'; 4 | 5 | export interface ReservoirMethods { 6 | drip(): Sendable; 7 | dripped(): Callable; 8 | dripStart(): Callable; 9 | dripRate(): Callable; 10 | token(): Callable; 11 | target(): Callable; 12 | } 13 | 14 | export interface Reservoir extends Contract { 15 | methods: ReservoirMethods; 16 | name: string; 17 | } 18 | -------------------------------------------------------------------------------- /scenario/src/Contract/TROP.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { encodedNumber } from '../Encoding'; 3 | import { Callable, Sendable } from '../Invokation'; 4 | 5 | interface Checkpoint { 6 | fromBlock: number; 7 | votes: number; 8 | } 9 | 10 | export interface CompMethods { 11 | name(): Callable; 12 | symbol(): Callable; 13 | decimals(): Callable; 14 | totalSupply(): Callable; 15 | balanceOf(address: string): Callable; 16 | allowance(owner: string, spender: string): Callable; 17 | approve(address: string, amount: encodedNumber): Sendable; 18 | transfer(address: string, amount: encodedNumber): Sendable; 19 | transferFrom(owner: string, spender: string, amount: encodedNumber): Sendable; 20 | checkpoints(account: string, index: number): Callable; 21 | numCheckpoints(account: string): Callable; 22 | delegate(account: string): Sendable; 23 | getCurrentVotes(account: string): Callable; 24 | getPriorVotes(account: string, blockNumber: encodedNumber): Callable; 25 | setBlockNumber(blockNumber: encodedNumber): Sendable; 26 | } 27 | 28 | export interface CompScenarioMethods extends CompMethods { 29 | transferScenario(destinations: string[], amount: encodedNumber): Sendable; 30 | transferFromScenario(froms: string[], amount: encodedNumber): Sendable; 31 | } 32 | 33 | export interface TROP extends Contract { 34 | methods: CompMethods; 35 | name: string; 36 | } 37 | 38 | export interface CompScenario extends Contract { 39 | methods: CompScenarioMethods; 40 | name: string; 41 | } 42 | -------------------------------------------------------------------------------- /scenario/src/Contract/Timelock.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | import { encodedNumber } from '../Encoding'; 4 | 5 | interface TimelockMethods { 6 | admin(): Callable; 7 | pendingAdmin(): Callable; 8 | delay(): Callable; 9 | queuedTransactions(txHash: string): Callable; 10 | setDelay(delay: encodedNumber): Sendable; 11 | acceptAdmin(): Sendable; 12 | setPendingAdmin(admin: string): Sendable; 13 | queueTransaction( 14 | target: string, 15 | value: encodedNumber, 16 | signature: string, 17 | data: string, 18 | eta: encodedNumber 19 | ): Sendable; 20 | cancelTransaction( 21 | target: string, 22 | value: encodedNumber, 23 | signature: string, 24 | data: string, 25 | eta: encodedNumber 26 | ): Sendable; 27 | executeTransaction( 28 | target: string, 29 | value: encodedNumber, 30 | signature: string, 31 | data: string, 32 | eta: encodedNumber 33 | ): Sendable; 34 | 35 | blockTimestamp(): Callable; 36 | harnessFastForward(seconds: encodedNumber): Sendable; 37 | harnessSetBlockTimestamp(seconds: encodedNumber): Sendable; 38 | harnessSetAdmin(admin: string): Sendable; 39 | } 40 | 41 | export interface Timelock extends Contract { 42 | methods: TimelockMethods; 43 | } 44 | -------------------------------------------------------------------------------- /scenario/src/Contract/TropykusLens.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { encodedNumber } from '../Encoding'; 3 | import { Callable, Sendable } from '../Invokation'; 4 | 5 | export interface TropykusLensMethods { 6 | cTokenBalances(cToken: string, account: string): Sendable<[string,number,number,number,number,number]>; 7 | cTokenBalancesAll(cTokens: string[], account: string): Sendable<[string,number,number,number,number,number][]>; 8 | cTokenMetadata(cToken: string): Sendable<[string,number,number,number,number,number,number,number,number,boolean,number,string,number,number]>; 9 | cTokenMetadataAll(cTokens: string[]): Sendable<[string,number,number,number,number,number,number,number,number,boolean,number,string,number,number][]>; 10 | cTokenUnderlyingPrice(cToken: string): Sendable<[string,number]>; 11 | cTokenUnderlyingPriceAll(cTokens: string[]): Sendable<[string,number][]>; 12 | getAccountLimits(comptroller: string, account: string): Sendable<[string[],number,number]>; 13 | } 14 | 15 | export interface TropykusLens extends Contract { 16 | methods: TropykusLensMethods; 17 | name: string; 18 | } 19 | -------------------------------------------------------------------------------- /scenario/src/Contract/Unitroller.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | 4 | interface UnitrollerMethods { 5 | admin(): Callable; 6 | pendingAdmin(): Callable; 7 | _acceptAdmin(): Sendable; 8 | _setPendingAdmin(pendingAdmin: string): Sendable; 9 | _setPendingImplementation(pendingImpl: string): Sendable; 10 | comptrollerImplementation(): Callable; 11 | pendingComptrollerImplementation(): Callable; 12 | } 13 | 14 | export interface Unitroller extends Contract { 15 | methods: UnitrollerMethods; 16 | } 17 | -------------------------------------------------------------------------------- /scenario/src/Contract/Vat.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../Contract'; 2 | import { Callable, Sendable } from '../Invokation'; 3 | import { encodedNumber } from '../Encoding'; 4 | 5 | interface VatMethods { 6 | dai(address: string): Callable; 7 | hope(address: string): Sendable; 8 | move(src: string, dst: string, amount: encodedNumber): Sendable; 9 | } 10 | 11 | export interface Vat extends Contract { 12 | methods: VatMethods; 13 | name: string; 14 | } 15 | -------------------------------------------------------------------------------- /scenario/src/Contract/builder.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | let [_, _f, buildFile, contract] = process.argv; 5 | 6 | if (!buildFile || !contract) { 7 | throw new Error(`builder.js `); 8 | } 9 | if (!fs.existsSync(buildFile)) { 10 | throw new Error(`build_file: file not found`); 11 | } 12 | let buildRaw = fs.readFileSync(buildFile, 'utf8'); 13 | let build; 14 | 15 | try { 16 | build = JSON.parse(buildRaw); 17 | } catch (e) { 18 | throw new Error(`Error parsing build file: ${e.toString()}`); 19 | } 20 | if (!build.contracts) { 21 | throw new Error(`Invalid build file, missing contracts`); 22 | } 23 | let contractInfo = Object.entries(build.contracts).find(([k,v]) => k.split(':')[1] === contract); 24 | if (!contractInfo) { 25 | throw new Error(`Build file does not contain info for ${contract}`); 26 | } 27 | let contractABI = JSON.parse(contractInfo[1].abi); 28 | 29 | console.log(`export interface ${contract}Methods {`); 30 | contractABI.forEach(abi => { 31 | if (abi.type === 'function') { 32 | function mapped(io) { 33 | let typeMap = { 34 | 'address': 'string', 35 | 'address[]': 'string[]', 36 | 'uint256': 'number', 37 | 'bool': 'boolean' 38 | }; 39 | return typeMap[io.type] || io.type; 40 | }; 41 | let name = abi.name; 42 | let args = abi.inputs.map((input) => { 43 | return `${input.name}: ${mapped(input)}`; 44 | }).join(', '); 45 | let returnType = abi.outputs.map((output) => { 46 | if (output.type == 'tuple' || output.type == 'tuple[]') { 47 | let res = output.components.map((c) => { 48 | return mapped(c); 49 | }).join(','); 50 | if (output.type == 'tuple[]') { 51 | return `[${res}][]`; 52 | } else { 53 | return `[${res}]`; 54 | } 55 | } else { 56 | return mapped(output); 57 | } 58 | }).join(','); 59 | let able = abi.constant ? 'Callable' : 'Sendable'; 60 | console.log(` ${name}(${args}): ${able}<${returnType}>;`); 61 | } 62 | }); 63 | console.log("}"); 64 | -------------------------------------------------------------------------------- /scenario/src/Encoding.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | const smallEnoughNumber = new BigNumber('100000000'); 4 | 5 | export type encodedNumber = number | BigNumber; 6 | 7 | // Returns the mantissa of an Exp with given floating value 8 | export function getExpMantissa(float: number): encodedNumber { 9 | // Workaround from https://github.com/ethereum/web3.js/issues/1920 10 | const str = Math.floor(float * 1.0e18).toString(); 11 | 12 | return toEncodableNum(str); 13 | } 14 | 15 | export function toEncodableNum(amountArgRaw: string | encodedNumber): encodedNumber { 16 | const bigNumber = new BigNumber(amountArgRaw); 17 | return bigNumber.lt(smallEnoughNumber) ? bigNumber.toNumber() : bigNumber; 18 | } 19 | -------------------------------------------------------------------------------- /scenario/src/Event.ts: -------------------------------------------------------------------------------- 1 | 2 | type ScalarEvent = string; 3 | interface EventArray extends Array { 4 | [index: number]: ScalarEvent | EventArray; 5 | } 6 | 7 | export type Event = EventArray; 8 | -------------------------------------------------------------------------------- /scenario/src/Event/TrxEvent.ts: -------------------------------------------------------------------------------- 1 | import {World} from '../World'; 2 | import {Event} from '../Event'; 3 | import {processCoreEvent} from '../CoreEvent'; 4 | import { 5 | EventV, 6 | NumberV 7 | } from '../Value'; 8 | import { 9 | getEventV, 10 | getNumberV 11 | } from '../CoreValue'; 12 | import {Arg, Command, processCommandEvent} from '../Command'; 13 | import {encodedNumber} from '../Encoding'; 14 | import BigNumber from 'bignumber.js'; 15 | 16 | BigNumber.config({ EXPONENTIAL_AT: [-20, 50] }) 17 | 18 | async function setTrxValue(world: World, value: encodedNumber): Promise { 19 | return world.update('trxInvokationOpts', (t) => t.set('value', value.toString())); 20 | } 21 | 22 | async function setTrxGasPrice(world: World, gasPrice: encodedNumber): Promise { 23 | return world.update('trxInvokationOpts', (t) => t.set('gasPrice', gasPrice.toString()));; 24 | } 25 | 26 | export function trxCommands() { 27 | return [ 28 | new Command<{amount: NumberV, event: EventV}>(` 29 | #### Value 30 | 31 | * "Value " - Runs event with a set amount for any transactions 32 | * E.g. "Value 1.0e18 (CToken cEth Mint 1.0e18)" 33 | `, 34 | "Value", 35 | [ 36 | new Arg("amount", getNumberV), 37 | new Arg("event", getEventV) 38 | ], 39 | async (world, from, {amount, event}) => processCoreEvent(await setTrxValue(world, amount.encode()), event.val, from) 40 | ), 41 | new Command<{gasPrice: NumberV, event: EventV}>(` 42 | #### GasPrice 43 | 44 | * "GasPrice " - Runs event with a given gas price 45 | * E.g. "GasPrice 0 (CToken cEth Mint 1.0e18)" 46 | `, 47 | "GasPrice", 48 | [ 49 | new Arg("gasPrice", getNumberV), 50 | new Arg("event", getEventV) 51 | ], 52 | async (world, from, {gasPrice, event}) => processCoreEvent(await setTrxGasPrice(world, gasPrice.encode()), event.val, from) 53 | ) 54 | ]; 55 | } 56 | 57 | export async function processTrxEvent(world: World, event: Event, from: string | null): Promise { 58 | return await processCommandEvent("Trx", trxCommands(), world, event, from); 59 | } 60 | -------------------------------------------------------------------------------- /scenario/src/Expectation.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Expectation { 3 | checker: (world: any) => Promise; 4 | } 5 | -------------------------------------------------------------------------------- /scenario/src/Expectation/ChangesExpectation.ts: -------------------------------------------------------------------------------- 1 | import {Expectation} from '../Expectation'; 2 | import {fail, World} from '../World'; 3 | import {getCoreValue} from '../CoreValue'; 4 | import {Value, NumberV} from '../Value'; 5 | import {Event} from '../Event'; 6 | import {formatEvent} from '../Formatter'; 7 | import {BigNumber} from 'bignumber.js'; 8 | 9 | function asNumberV(v: Value): NumberV { 10 | if (v instanceof NumberV) { 11 | return v; 12 | } else { 13 | throw new Error(`Expected NumberV for ChangesExpectation, got ${v.toString()}`); 14 | } 15 | } 16 | 17 | export class ChangesExpectation implements Expectation { 18 | condition: Event; 19 | originalValue: NumberV; 20 | delta: NumberV; 21 | tolerance: NumberV; 22 | expected: NumberV; 23 | 24 | constructor(condition: Event, originalValue: Value, delta: NumberV, tolerance: NumberV) { 25 | this.condition = condition; 26 | this.originalValue = asNumberV(originalValue); 27 | this.delta = delta; 28 | this.tolerance = tolerance; 29 | this.expected = this.originalValue.add(this.delta); 30 | } 31 | 32 | async getCurrentValue(world: World): Promise { 33 | return await getCoreValue(world, this.condition); 34 | }; 35 | 36 | async checker(world: World, initialCheck: boolean=false): Promise { 37 | const currentValue = asNumberV(await this.getCurrentValue(world)); 38 | const trueDelta = currentValue.sub(this.originalValue); 39 | 40 | if (this.tolerance.val != 0) { 41 | if (Math.abs(Number(trueDelta.sub(this.delta).div(this.originalValue).val)) > Number(this.tolerance.val)) { 42 | fail(world, `Expected ${trueDelta.toString()} to approximately equal ${this.delta.toString()} within ${this.tolerance.toString()}`); 43 | } 44 | } else if (!currentValue.compareTo(world, this.expected)) { 45 | fail(world, `${this.toString()} instead had value \`${currentValue.toString()}\` (true delta: ${trueDelta.toString()})`); 46 | } 47 | } 48 | 49 | toString() { 50 | return `ChangesExpectation: condition=${formatEvent(this.condition)}, originalValue=${this.originalValue.toString()}, delta=${this.delta.toString()}, expected=${this.expected.toString()}`; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scenario/src/Expectation/RemainsExpectation.ts: -------------------------------------------------------------------------------- 1 | import {Expectation} from '../Expectation'; 2 | import {fail, World} from '../World'; 3 | import {getCoreValue} from '../CoreValue'; 4 | import {Value} from '../Value'; 5 | import {Event} from '../Event'; 6 | import {formatEvent} from '../Formatter'; 7 | 8 | export class RemainsExpectation implements Expectation { 9 | condition: Event; 10 | value: Value; 11 | 12 | constructor(condition: Event, value: Value) { 13 | this.condition = condition; 14 | this.value = value; 15 | } 16 | 17 | async getCurrentValue(world: World): Promise { 18 | return await getCoreValue(world, this.condition); 19 | }; 20 | 21 | async checker(world: World, initialCheck: boolean=false): Promise { 22 | const currentValue = await this.getCurrentValue(world); 23 | 24 | if (!this.value.compareTo(world, currentValue)) { 25 | fail(world, `${this.toString()} failed as value ${initialCheck ? 'started as' : 'became'} \`${currentValue.toString()}\``); 26 | } 27 | } 28 | 29 | toString() { 30 | return `RemainsExpectation: condition=${formatEvent(this.condition)}, value=${this.value.toString()}`; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scenario/src/File.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { World } from './World'; 4 | 5 | export function getNetworkPath(basePath: string | null, network: string, name: string, extension: string | null='json'): string { 6 | return path.join(basePath || '', 'networks', `${network}${name}${extension ? `.${extension}` : ''}`); 7 | } 8 | 9 | export async function readFile(world: World | null, file: string, def: T, fn: (data: string) => T): Promise { 10 | if (world && world.fs) { 11 | let data = world.fs[file]; 12 | return Promise.resolve(data ? fn(data) : def); 13 | } else { 14 | return new Promise((resolve, reject) => { 15 | fs.access(file, fs.constants.F_OK, (err) => { 16 | if (err) { 17 | resolve(def); 18 | } else { 19 | fs.readFile(file, 'utf8', (err, data) => { 20 | return err ? reject(err) : resolve(fn(data)); 21 | }); 22 | } 23 | }); 24 | }); 25 | } 26 | } 27 | 28 | export async function writeFile(world: World | null, file: string, data: string): Promise { 29 | if (world && world.fs) { 30 | world = world.setIn(['fs', file], data); 31 | return Promise.resolve(world); 32 | } else { 33 | return new Promise((resolve, reject) => { 34 | fs.writeFile(file, data, (err) => { 35 | return err ? reject(err) : resolve(world!); // XXXS `!` 36 | }); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scenario/src/Formatter.ts: -------------------------------------------------------------------------------- 1 | import {Event} from './Event'; 2 | 3 | // Effectively the opposite of parse 4 | export function formatEvent(event: Event, outter=true): string { 5 | if (Array.isArray(event)) { 6 | if (event.length === 2 && typeof event[0] === "string" && (event[0]).toLowerCase() === "exactly") { 7 | return event[1].toString(); 8 | } 9 | 10 | let mapped = event.map(e => formatEvent(e, false)); 11 | let joined = mapped.join(' '); 12 | 13 | if (outter) { 14 | return joined; 15 | } else { 16 | return `(${joined})`; 17 | } 18 | } else { 19 | return event; 20 | } 21 | } 22 | 23 | export function formatError(err: any) { 24 | return JSON.stringify(err); // yeah... for now 25 | } 26 | -------------------------------------------------------------------------------- /scenario/src/Help.ts: -------------------------------------------------------------------------------- 1 | import {Event} from './Event'; 2 | import {Expression} from './Command'; 3 | import {mustString} from './Utils'; 4 | import {Printer} from './Printer'; 5 | 6 | export function printHelp(printer: Printer, event: Event, expressions: Expression[], path: string[]=[]) { 7 | if (event.length === 0) { 8 | let banner; 9 | 10 | if (path.length === 0) { 11 | banner = ( 12 | ` 13 | ## Compound Command Runner 14 | 15 | The Compound Command Runner makes it easy to interact with Compound. You can input simple commands 16 | and it will construct Web3 calls to pull data or generate transactions. A list of available commands 17 | is included below. To dig further into a command run \`Help \`, such as \`Help From\` or for 18 | sub-commands run \`Help CToken\` or \`Help CToken Mint\`. 19 | `).trim(); 20 | } else { 21 | if (expressions.length > 0) { 22 | banner = `### ${path.join(" ")} Sub-Commands`; 23 | } 24 | } 25 | 26 | if (!!banner) { 27 | printer.printMarkdown(banner); 28 | } 29 | 30 | expressions.forEach((expression) => { 31 | printer.printMarkdown(`\n${expression.doc}`); 32 | if (expression.subExpressions.length > 0) { 33 | printer.printMarkdown(`For more information, run: \`Help ${path} ${expression.name}\``); 34 | } 35 | }); 36 | } else { 37 | const [first, ...rest] = event; 38 | const expressionName = mustString(first); 39 | 40 | let expression = expressions.find((expression) => expression.name.toLowerCase() === expressionName.toLowerCase()); 41 | 42 | if (expression) { 43 | if (rest.length === 0) { 44 | printer.printMarkdown(`${expression.doc}`); 45 | } 46 | 47 | printHelp(printer, rest, expression.subExpressions, path.concat(expression.name)); 48 | } else { 49 | let matchingExpressions = expressions.filter((expression) => expression.name.toLowerCase().startsWith(expressionName.toLowerCase())); 50 | 51 | if (matchingExpressions.length === 0) { 52 | printer.printLine(`\nError: cannot find help docs for ${path.concat(first).join(" ")}`); 53 | } else { 54 | if (rest.length === 0) { 55 | matchingExpressions.forEach((expression) => { 56 | printer.printMarkdown(`${expression.doc}`); 57 | }); 58 | } else { 59 | printer.printLine(`\nError: cannot find help docs for ${path.concat(event).join(" ")}`); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /scenario/src/HistoricReadline.ts: -------------------------------------------------------------------------------- 1 | import * as readline from 'readline'; 2 | import * as fs from 'fs'; 3 | import {readFile} from './File'; 4 | 5 | let readlineAny = readline; 6 | 7 | export async function createInterface(options): Promise { 8 | let history: string[] = await readFile(null, options['path'], [], (x) => x.split("\n")); 9 | let cleanHistory = history.filter((x) => !!x).reverse(); 10 | 11 | readlineAny.kHistorySize = Math.max(readlineAny.kHistorySize, options['maxLength']); 12 | 13 | let rl = readline.createInterface(options); 14 | let rlAny = rl; 15 | 16 | let oldAddHistory = rlAny._addHistory; 17 | 18 | rlAny._addHistory = function() { 19 | let last = rlAny.history[0]; 20 | let line = oldAddHistory.call(rl); 21 | 22 | // TODO: Should this be sync? 23 | if (line.length > 0 && line != last) { 24 | fs.appendFileSync(options['path'], `${line}\n`); 25 | } 26 | 27 | // TODO: Truncate file? 28 | 29 | return line; 30 | } 31 | 32 | rlAny.history.push.apply(rlAny.history, cleanHistory); 33 | 34 | return rl; 35 | } 36 | -------------------------------------------------------------------------------- /scenario/src/Hypothetical.ts: -------------------------------------------------------------------------------- 1 | import {Accounts, loadAccounts} from './Accounts'; 2 | import { 3 | addAction, 4 | checkExpectations, 5 | checkInvariants, 6 | clearInvariants, 7 | describeUser, 8 | holdInvariants, 9 | setEvent, 10 | World 11 | } from './World'; 12 | import {Ganache} from 'eth-saddle/dist/config'; 13 | import Web3 from 'web3'; 14 | 15 | export async function forkWeb3(web3: Web3, url: string, accounts: string[]): Promise { 16 | let lastBlock = await web3.eth.getBlock("latest") 17 | return new Web3( 18 | Ganache.provider({ 19 | allowUnlimitedContractSize: true, 20 | fork: url, 21 | gasLimit: lastBlock.gasLimit, // maintain configured gas limit 22 | gasPrice: '20000', 23 | port: 8546, 24 | unlocked_accounts: accounts 25 | }) 26 | ); 27 | } 28 | 29 | export async function fork(world: World, url: string, accounts: string[]): Promise { 30 | let newWeb3 = await forkWeb3(world.web3, url, accounts); 31 | const newAccounts = loadAccounts(await newWeb3.eth.getAccounts()); 32 | 33 | return world 34 | .set('web3', newWeb3) 35 | .set('accounts', newAccounts); 36 | } 37 | -------------------------------------------------------------------------------- /scenario/src/Invariant.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Invariant { 3 | held: boolean 4 | checker: (world: any) => Promise; 5 | } 6 | -------------------------------------------------------------------------------- /scenario/src/Invariant/RemainsInvariant.ts: -------------------------------------------------------------------------------- 1 | import {Invariant} from '../Invariant'; 2 | import {fail, World} from '../World'; 3 | import {getCoreValue} from '../CoreValue'; 4 | import {Value} from '../Value'; 5 | import {Event} from '../Event'; 6 | import {formatEvent} from '../Formatter'; 7 | 8 | export class RemainsInvariant implements Invariant { 9 | condition: Event; 10 | value: Value; 11 | held = false; 12 | 13 | constructor(condition: Event, value: Value) { 14 | this.condition = condition; 15 | this.value = value; 16 | } 17 | 18 | async getCurrentValue(world: World): Promise { 19 | return await getCoreValue(world, this.condition); 20 | }; 21 | 22 | async checker(world: World, initialCheck: boolean=false): Promise { 23 | const currentValue = await this.getCurrentValue(world); 24 | 25 | if (!this.value.compareTo(world, currentValue)) { 26 | fail(world, `Static invariant broken! Expected ${this.toString()} to remain static value \`${this.value}\` but ${initialCheck ? 'started as' : 'became'} \`${currentValue}\``); 27 | } 28 | } 29 | 30 | toString() { 31 | return `RemainsInvariant: condition=${formatEvent(this.condition)}, value=${this.value.toString()}`; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scenario/src/Invariant/StaticInvariant.ts: -------------------------------------------------------------------------------- 1 | import {Invariant} from '../Invariant'; 2 | import {fail, World} from '../World'; 3 | import {getCoreValue} from '../CoreValue'; 4 | import {Value} from '../Value'; 5 | import {Event} from '../Event'; 6 | import {formatEvent} from '../Formatter'; 7 | 8 | export class StaticInvariant implements Invariant { 9 | condition: Event; 10 | value: Value; 11 | held = false; 12 | 13 | constructor(condition: Event, value: Value) { 14 | this.condition = condition; 15 | this.value = value; 16 | } 17 | 18 | async getCurrentValue(world: World): Promise { 19 | return await getCoreValue(world, this.condition); 20 | }; 21 | 22 | async checker(world: World): Promise { 23 | const currentValue = await this.getCurrentValue(world); 24 | 25 | if (!this.value.compareTo(world, currentValue)) { 26 | fail(world, `Static invariant broken! Expected ${this.toString()} to remain static value \`${this.value}\` but became \`${currentValue}\``); 27 | } 28 | } 29 | 30 | toString() { 31 | return `StaticInvariant: condition=${formatEvent(this.condition)}, value=${this.value.toString()}`; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scenario/src/Invariant/SuccessInvariant.ts: -------------------------------------------------------------------------------- 1 | import {Invariant} from '../Invariant'; 2 | import {fail, World} from '../World'; 3 | import {getCoreValue} from '../CoreValue'; 4 | import {Value} from '../Value'; 5 | import {Event} from '../Event'; 6 | 7 | export class SuccessInvariant implements Invariant { 8 | held = false; 9 | 10 | constructor() {} 11 | 12 | async checker(world: World): Promise { 13 | if (world.lastInvokation && !world.lastInvokation.success()) { 14 | fail(world, `Success invariant broken! Expected successful execution, but had error ${world.lastInvokation.toString()}`); 15 | } 16 | } 17 | 18 | toString() { 19 | return `SuccessInvariant`; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scenario/src/Repl.d.ts: -------------------------------------------------------------------------------- 1 | import {Artifacts} from './Artifact'; 2 | import {Web3} from './Web3'; 3 | 4 | declare namespace NodeJS { 5 | interface Global { 6 | Web3: Web3 7 | Artifacts: Artifacts 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /scenario/src/Runner.ts: -------------------------------------------------------------------------------- 1 | import {World} from './World'; 2 | import {parse} from './Parser'; 3 | import {expandEvent, Macros} from './Macro'; 4 | import {processEvents} from './CoreEvent' 5 | 6 | export async function runCommand(world: World, command: string, macros: Macros): Promise { 7 | const trimmedCommand = command.trim(); 8 | 9 | const event = parse(trimmedCommand, {startRule: 'step'}); 10 | 11 | if (event === null) { 12 | return world; 13 | } else { 14 | world.printer.printLine(`Command: ${trimmedCommand}`); 15 | 16 | let expanded = expandEvent(macros, event); 17 | 18 | return processEvents(world, expanded); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scenario/src/Settings.ts: -------------------------------------------------------------------------------- 1 | import { getNetworkPath, readFile, writeFile } from './File'; 2 | 3 | export class Settings { 4 | basePath: string | null; 5 | network: string | null; 6 | aliases: { [name: string]: string }; 7 | from: string | undefined; 8 | printTxLogs: boolean = false; 9 | 10 | constructor( 11 | basePath: string | null, 12 | network: string | null, 13 | aliases: { [name: string]: string }, 14 | from?: string 15 | ) { 16 | this.basePath = basePath; 17 | this.network = network; 18 | this.aliases = aliases; 19 | this.from = from; 20 | } 21 | 22 | static deserialize(basePath: string, network: string, data: string): Settings { 23 | const { aliases } = JSON.parse(data); 24 | 25 | return new Settings(basePath, network, aliases); 26 | } 27 | 28 | serialize(): string { 29 | return JSON.stringify({ 30 | aliases: this.aliases 31 | }); 32 | } 33 | 34 | static default(basePath: string | null, network: string | null): Settings { 35 | return new Settings(basePath, network, {}); 36 | } 37 | 38 | static getFilePath(basePath: string | null, network: string): string { 39 | return getNetworkPath(basePath, network, '-settings'); 40 | } 41 | 42 | static load(basePath: string, network: string): Promise { 43 | return readFile(null, Settings.getFilePath(basePath, network), Settings.default(basePath, network), data => 44 | Settings.deserialize(basePath, network, data) 45 | ); 46 | } 47 | 48 | async save(): Promise { 49 | if (this.network) { 50 | await writeFile(null, Settings.getFilePath(this.basePath, this.network), this.serialize()); 51 | } 52 | } 53 | 54 | lookupAlias(address: string): string { 55 | let entry = Object.entries(this.aliases).find(([key, value]) => { 56 | return value === address; 57 | }); 58 | 59 | if (entry) { 60 | return entry[0]; 61 | } else { 62 | return address; 63 | } 64 | } 65 | 66 | lookupAliases(address: string): string[] { 67 | let entries = Object.entries(this.aliases).filter(([key, value]) => { 68 | return value === address; 69 | }); 70 | 71 | return entries.map(([key, _value]) => key); 72 | } 73 | 74 | findAlias(name: string): string | null { 75 | const alias = Object.entries(this.aliases).find( 76 | ([alias, addr]) => alias.toLowerCase() === name.toLowerCase() 77 | ); 78 | 79 | if (alias) { 80 | return alias[1]; 81 | } else { 82 | return null; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /scenario/src/Value/CTokenDelegateValue.ts: -------------------------------------------------------------------------------- 1 | import { Event } from '../Event'; 2 | import { World } from '../World'; 3 | import { CErc20Delegate } from '../Contract/CErc20Delegate'; 4 | import { 5 | getCoreValue, 6 | mapValue 7 | } from '../CoreValue'; 8 | import { Arg, Fetcher, getFetcherValue } from '../Command'; 9 | import { 10 | AddressV, 11 | Value, 12 | } from '../Value'; 13 | import { getWorldContractByAddress, getCTokenDelegateAddress } from '../ContractLookup'; 14 | 15 | export async function getCTokenDelegateV(world: World, event: Event): Promise { 16 | const address = await mapValue( 17 | world, 18 | event, 19 | (str) => new AddressV(getCTokenDelegateAddress(world, str)), 20 | getCoreValue, 21 | AddressV 22 | ); 23 | 24 | return getWorldContractByAddress(world, address.val); 25 | } 26 | 27 | async function cTokenDelegateAddress(world: World, cTokenDelegate: CErc20Delegate): Promise { 28 | return new AddressV(cTokenDelegate._address); 29 | } 30 | 31 | export function cTokenDelegateFetchers() { 32 | return [ 33 | new Fetcher<{ cTokenDelegate: CErc20Delegate }, AddressV>(` 34 | #### Address 35 | 36 | * "CTokenDelegate Address" - Returns address of CTokenDelegate contract 37 | * E.g. "CTokenDelegate cDaiDelegate Address" - Returns cDaiDelegate's address 38 | `, 39 | "Address", 40 | [ 41 | new Arg("cTokenDelegate", getCTokenDelegateV) 42 | ], 43 | (world, { cTokenDelegate }) => cTokenDelegateAddress(world, cTokenDelegate), 44 | { namePos: 1 } 45 | ), 46 | ]; 47 | } 48 | 49 | export async function getCTokenDelegateValue(world: World, event: Event): Promise { 50 | return await getFetcherValue("CTokenDelegate", cTokenDelegateFetchers(), world, event); 51 | } 52 | -------------------------------------------------------------------------------- /scenario/src/Value/ComptrollerImplValue.ts: -------------------------------------------------------------------------------- 1 | import {Event} from '../Event'; 2 | import {World} from '../World'; 3 | import {ComptrollerImpl} from '../Contract/ComptrollerImpl'; 4 | import { 5 | getAddressV 6 | } from '../CoreValue'; 7 | import { 8 | AddressV, 9 | Value 10 | } from '../Value'; 11 | import {Arg, Fetcher, getFetcherValue} from '../Command'; 12 | import {getComptrollerImpl} from '../ContractLookup'; 13 | 14 | export async function getComptrollerImplAddress(world: World, comptrollerImpl: ComptrollerImpl): Promise { 15 | return new AddressV(comptrollerImpl._address); 16 | } 17 | 18 | export function comptrollerImplFetchers() { 19 | return [ 20 | new Fetcher<{comptrollerImpl: ComptrollerImpl}, AddressV>(` 21 | #### Address 22 | 23 | * "ComptrollerImpl Address" - Returns address of comptroller implementation 24 | `, 25 | "Address", 26 | [new Arg("comptrollerImpl", getComptrollerImpl)], 27 | (world, {comptrollerImpl}) => getComptrollerImplAddress(world, comptrollerImpl), 28 | {namePos: 1} 29 | ) 30 | ]; 31 | } 32 | 33 | export async function getComptrollerImplValue(world: World, event: Event): Promise { 34 | return await getFetcherValue("ComptrollerImpl", comptrollerImplFetchers(), world, event); 35 | } 36 | -------------------------------------------------------------------------------- /scenario/src/Value/InterestRateModelValue.ts: -------------------------------------------------------------------------------- 1 | import {Event} from '../Event'; 2 | import {World} from '../World'; 3 | import {InterestRateModel} from '../Contract/InterestRateModel'; 4 | import { 5 | getAddressV, 6 | getNumberV 7 | } from '../CoreValue'; 8 | import { 9 | AddressV, 10 | NumberV, 11 | Value} from '../Value'; 12 | import {Arg, Fetcher, getFetcherValue} from '../Command'; 13 | import {getInterestRateModel} from '../ContractLookup'; 14 | 15 | export async function getInterestRateModelAddress(world: World, interestRateModel: InterestRateModel): Promise { 16 | return new AddressV(interestRateModel._address); 17 | } 18 | 19 | export async function getBorrowRate(world: World, interestRateModel: InterestRateModel, cash: NumberV, borrows: NumberV, reserves: NumberV): Promise { 20 | return new NumberV(await interestRateModel.methods.getBorrowRate(cash.encode(), borrows.encode(), reserves.encode()).call(), 1.0e18 / 1051200); 21 | } 22 | 23 | export function interestRateModelFetchers() { 24 | return [ 25 | new Fetcher<{interestRateModel: InterestRateModel}, AddressV>(` 26 | #### Address 27 | 28 | * " Address" - Gets the address of the interest rate model 29 | * E.g. "InterestRateModel MyInterestRateModel Address" 30 | `, 31 | "Address", 32 | [ 33 | new Arg("interestRateModel", getInterestRateModel) 34 | ], 35 | (world, {interestRateModel}) => getInterestRateModelAddress(world, interestRateModel), 36 | {namePos: 1} 37 | ), 38 | 39 | new Fetcher<{interestRateModel: InterestRateModel, cash: NumberV, borrows: NumberV, reserves: NumberV}, NumberV>(` 40 | #### BorrowRate 41 | 42 | * " BorrowRate" - Gets the borrow rate of the interest rate model 43 | * E.g. "InterestRateModel MyInterestRateModel BorrowRate 0 10 0" 44 | `, 45 | "BorrowRate", 46 | [ 47 | new Arg("interestRateModel", getInterestRateModel), 48 | new Arg("cash", getNumberV), 49 | new Arg("borrows", getNumberV), 50 | new Arg("reserves", getNumberV) 51 | ], 52 | (world, {interestRateModel, cash, borrows, reserves}) => getBorrowRate(world, interestRateModel, cash, borrows, reserves), 53 | {namePos: 1} 54 | ) 55 | ]; 56 | } 57 | 58 | export async function getInterestRateModelValue(world: World, event: Event): Promise { 59 | return await getFetcherValue("InterestRateModel", interestRateModelFetchers(), world, event); 60 | } 61 | -------------------------------------------------------------------------------- /scenario/src/Value/MCDValue.ts: -------------------------------------------------------------------------------- 1 | import { Event } from '../Event'; 2 | import { World } from '../World'; 3 | import { getContract } from '../Contract'; 4 | import { Pot } from '../Contract/Pot'; 5 | import { Vat } from '../Contract/Vat'; 6 | import { 7 | getAddressV, 8 | getCoreValue, 9 | getStringV 10 | } from '../CoreValue'; 11 | import { Arg, Fetcher, getFetcherValue } from '../Command'; 12 | import { 13 | AddressV, 14 | NumberV, 15 | Value, 16 | StringV 17 | } from '../Value'; 18 | 19 | export function mcdFetchers() { 20 | return [ 21 | new Fetcher<{ potAddress: AddressV, method: StringV, args: StringV[] }, Value>(` 22 | #### PotAt 23 | 24 | * "MCD PotAt " 25 | * E.g. "MCD PotAt "0xPotAddress" "pie" (CToken cDai Address)" 26 | `, 27 | "PotAt", 28 | [ 29 | new Arg("potAddress", getAddressV), 30 | new Arg("method", getStringV), 31 | new Arg('args', getCoreValue, { variadic: true, mapped: true }) 32 | ], 33 | async (world, { potAddress, method, args }) => { 34 | const PotContract = getContract('PotLike'); 35 | const pot = await PotContract.at(world, potAddress.val); 36 | const argStrings = args.map(arg => arg.val); 37 | return new NumberV(await pot.methods[method.val](...argStrings).call()) 38 | } 39 | ), 40 | 41 | new Fetcher<{ vatAddress: AddressV, method: StringV, args: StringV[] }, Value>(` 42 | #### VatAt 43 | 44 | * "MCD VatAt " 45 | * E.g. "MCD VatAt "0xVatAddress" "dai" (CToken cDai Address)" 46 | `, 47 | "VatAt", 48 | [ 49 | new Arg("vatAddress", getAddressV), 50 | new Arg("method", getStringV), 51 | new Arg('args', getCoreValue, { variadic: true, mapped: true }) 52 | ], 53 | async (world, { vatAddress, method, args }) => { 54 | const VatContract = getContract('VatLike'); 55 | const vat = await VatContract.at(world, vatAddress.val); 56 | const argStrings = args.map(arg => arg.val); 57 | return new NumberV(await vat.methods[method.val](...argStrings).call()) 58 | } 59 | ) 60 | ]; 61 | } 62 | 63 | export async function getMCDValue(world: World, event: Event): Promise { 64 | return await getFetcherValue("MCD", mcdFetchers(), world, event); 65 | } 66 | -------------------------------------------------------------------------------- /scenario/src/Value/MaximillionValue.ts: -------------------------------------------------------------------------------- 1 | import {Event} from '../Event'; 2 | import {World} from '../World'; 3 | import {Maximillion} from '../Contract/Maximillion'; 4 | import { 5 | getAddressV 6 | } from '../CoreValue'; 7 | import { 8 | AddressV, 9 | Value 10 | } from '../Value'; 11 | import {Arg, Fetcher, getFetcherValue} from '../Command'; 12 | import {getMaximillion} from '../ContractLookup'; 13 | 14 | export async function getMaximillionAddress(world: World, maximillion: Maximillion): Promise { 15 | return new AddressV(maximillion._address); 16 | } 17 | 18 | export function maximillionFetchers() { 19 | return [ 20 | new Fetcher<{maximillion: Maximillion}, AddressV>(` 21 | #### Address 22 | 23 | * "Maximillion Address" - Returns address of maximillion 24 | `, 25 | "Address", 26 | [new Arg("maximillion", getMaximillion, {implicit: true})], 27 | (world, {maximillion}) => getMaximillionAddress(world, maximillion) 28 | ) 29 | ]; 30 | } 31 | 32 | export async function getMaximillionValue(world: World, event: Event): Promise { 33 | return await getFetcherValue("Maximillion", maximillionFetchers(), world, event); 34 | } 35 | -------------------------------------------------------------------------------- /scenario/src/Value/PriceOracleValue.ts: -------------------------------------------------------------------------------- 1 | import {Event} from '../Event'; 2 | import {World} from '../World'; 3 | import {PriceOracle} from '../Contract/PriceOracle'; 4 | import { 5 | getAddressV 6 | } from '../CoreValue'; 7 | import { 8 | AddressV, 9 | NumberV, 10 | Value} from '../Value'; 11 | import {Arg, Fetcher, getFetcherValue} from '../Command'; 12 | import {getPriceOracle} from '../ContractLookup'; 13 | 14 | async function getPrice(world: World, priceOracle: PriceOracle, asset: string): Promise { 15 | return new NumberV(await priceOracle.methods.assetPrices(asset).call()); 16 | } 17 | 18 | export async function getPriceOracleAddress(world: World, priceOracle: PriceOracle): Promise { 19 | return new AddressV(priceOracle._address); 20 | } 21 | 22 | export function priceOracleFetchers() { 23 | return [ 24 | new Fetcher<{priceOracle: PriceOracle}, AddressV>(` 25 | #### Address 26 | 27 | * "Address" - Gets the address of the global price oracle 28 | `, 29 | "Address", 30 | [ 31 | new Arg("priceOracle", getPriceOracle, {implicit: true}) 32 | ], 33 | (world, {priceOracle}) => getPriceOracleAddress(world, priceOracle) 34 | ), 35 | new Fetcher<{priceOracle: PriceOracle, asset: AddressV}, NumberV>(` 36 | #### Price 37 | 38 | * "Price asset:
" - Gets the price of the given asset 39 | `, 40 | "Price", 41 | [ 42 | new Arg("priceOracle", getPriceOracle, {implicit: true}), 43 | new Arg("asset", getAddressV,) 44 | ], 45 | (world, {priceOracle, asset}) => getPrice(world, priceOracle, asset.val) 46 | ) 47 | ]; 48 | } 49 | 50 | export async function getPriceOracleValue(world: World, event: Event): Promise { 51 | return await getFetcherValue("PriceOracle", priceOracleFetchers(), world, event); 52 | } 53 | -------------------------------------------------------------------------------- /scenario/src/Value/UserValue.ts: -------------------------------------------------------------------------------- 1 | import {Event} from '../Event'; 2 | import {World} from '../World'; 3 | import { 4 | getAddressV 5 | } from '../CoreValue'; 6 | import {Arg, Fetcher, getFetcherValue} from '../Command'; 7 | import { 8 | AddressV, 9 | Value 10 | } from '../Value'; 11 | 12 | async function getUserAddress(world: World, user: string): Promise { 13 | return new AddressV(user); 14 | } 15 | 16 | export function userFetchers() { 17 | return [ 18 | new Fetcher<{account: AddressV}, AddressV>(` 19 | #### Address 20 | 21 | * "User Address" - Returns address of user 22 | * E.g. "User Geoff Address" - Returns Geoff's address 23 | `, 24 | "Address", 25 | [ 26 | new Arg("account", getAddressV) 27 | ], 28 | async (world, {account}) => account, 29 | {namePos: 1} 30 | ) 31 | ]; 32 | } 33 | 34 | export async function getUserValue(world: World, event: Event): Promise { 35 | return await getFetcherValue("User", userFetchers(), world, event); 36 | } 37 | -------------------------------------------------------------------------------- /scenario/src/Web.ts: -------------------------------------------------------------------------------- 1 | import { parse } from './Parser'; 2 | import { World, initWorld } from './World'; 3 | import { throwExpect } from './Assert'; 4 | import { CallbackPrinter } from './Printer'; 5 | import { runCommand } from './Runner'; 6 | import { loadContractData, parseNetworkFile } from './Networks'; 7 | import Web3 from 'web3'; 8 | import { Saddle } from 'eth-saddle'; 9 | 10 | function networkFromId(id: number) { 11 | switch (id) { 12 | case 0: 13 | return 'olympic'; 14 | 15 | case 1: 16 | return 'mainnet'; 17 | 18 | case 2: 19 | return 'morden'; 20 | 21 | case 3: 22 | return 'ropsten'; 23 | 24 | case 4: 25 | return 'rinkeby'; 26 | 27 | case 5: 28 | return 'goerli'; 29 | 30 | case 8: 31 | return 'ubiq'; 32 | 33 | case 42: 34 | return 'kovan'; 35 | 36 | case 77: 37 | return 'sokol'; 38 | 39 | case 99: 40 | return 'core'; 41 | 42 | case 999: 43 | return 'development'; 44 | 45 | default: 46 | return ''; 47 | } 48 | } 49 | 50 | export async function webWorld( 51 | web3: Web3, 52 | networksData: string, 53 | networksABIData: string, 54 | printerCallback: (message: any) => void 55 | ): Promise { 56 | let printer = new CallbackPrinter(printerCallback); 57 | let accounts; 58 | if (web3.currentProvider && typeof(web3.currentProvider) !== 'string') { 59 | // XXXS 60 | accounts = [(web3.currentProvider).address]; 61 | } 62 | 63 | const networkId = await (web3 as any).net.getId(); 64 | const network: string = networkFromId(networkId); 65 | 66 | // XXXS 67 | const saddle = { 68 | web3: web3 69 | }; 70 | 71 | let world = await initWorld(throwExpect, printer, web3, saddle, network, accounts, null, null); 72 | 73 | let networks = parseNetworkFile(networksData); 74 | let networksABI = parseNetworkFile(networksABIData); 75 | 76 | [world] = await loadContractData(world, networks, networksABI); 77 | // world = loadInvokationOpts(world); 78 | // world = loadVerbose(world); 79 | // world = loadDryRun(world); 80 | // world = await loadSettings(world); 81 | 82 | return world; 83 | } 84 | 85 | export async function webParse(world: World, line: string): Promise { 86 | return runCommand(world, line, {}); 87 | } 88 | -------------------------------------------------------------------------------- /scenario/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | stats: 'verbose', 6 | devtool: 'source-map', 7 | externals: { 8 | file: '{}', 9 | fs: '{}', 10 | tls: '{}', 11 | net: '{}', 12 | xmlhttprequest: '{}', 13 | 'truffle-flattener': '{}', 14 | 'request': '{}' 15 | }, 16 | optimization: { 17 | minimize: true 18 | }, 19 | plugins: [ 20 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 21 | new webpack.DefinePlugin({ 22 | 'process.env': { 23 | // This has effect on the react lib size 24 | 'NODE_ENV': JSON.stringify('production'), 25 | } 26 | }) 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /script/README.md: -------------------------------------------------------------------------------- 1 | Scripts to make common developer tasks easy to type. 2 | -------------------------------------------------------------------------------- /script/build_scenarios: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | root_dir="$(cd $dir/.. && pwd)" 7 | tests_dir="$(cd $root_dir/tests && pwd)" 8 | scenario_dir="$tests_dir/Scenarios" 9 | cache_file="$root_dir/.scencache" 10 | checksum="$(echo $root_dir/spec/scenario{,/**}/*.scen | xargs shasum| shasum| cut -d' ' -f 1)" 11 | 12 | if [ -z "$rebuild" -a -f "$cache_file" ]; then 13 | cached="$(cat $cache_file)" 14 | 15 | if [ "$cached" == "$checksum" ]; then 16 | echo "Skipping Scenario Stub Rebuild (set rebuild=true to force)" 17 | exit 0 18 | fi 19 | fi 20 | 21 | echo "Build scenario stubs..." 22 | rm -rf "$scenario_dir" && mkdir "$scenario_dir" 23 | 24 | scenario_test="$(cat <<-EOF 25 | const scenario = require('REL_PATHScenario'); 26 | 27 | scenario.run('SCEN_FILE'); 28 | EOF 29 | )" 30 | 31 | cd $root_dir/spec/scenario 32 | 33 | for scenario in .{,/**}/*.scen; do 34 | base="${scenario#.\/}" 35 | filename="${base%.*}ScenTest.js" 36 | final="$scenario_dir/$filename" 37 | mkdir -p "$(dirname "$final")" 38 | rel_path="$(echo "$scenario" | sed 's$[^/]$$g' | sed 's$/$../$g')" 39 | echo "$scenario_test" | sed "s^SCEN_FILE^$base^g" | sed "s^REL_PATH^$rel_path^g" > "$final" 40 | done 41 | 42 | echo "$checksum" > "$cache_file" 43 | -------------------------------------------------------------------------------- /script/compile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | root_dir="$(cd $dir/.. && pwd)" 7 | cache_file="$root_dir/.solcache" 8 | saddle_config_file="$root_dir/saddle.config.js" 9 | config_trace=`node -e "console.log(require(\"$saddle_config_file\").trace)"` 10 | [[ "$*" == "--trace" ]] || [[ "$config_trace" == "true" ]] && cache_file="${cache_file}cov" 11 | checksum="$(ls $root_dir/{contracts,contracts/**,tests/Contracts}/*.sol | xargs shasum| shasum| cut -d' ' -f 1)" 12 | 13 | if [ -z "$rebuild" -a -f "$cache_file" ]; then 14 | cached="$(cat $cache_file)" 15 | 16 | if [ "$cached" == "$checksum" ]; then 17 | echo "Skipping Contract Rebuild (set rebuild=true to force)" 18 | exit 0 19 | fi 20 | fi 21 | 22 | echo "Compiling Solidity contracts..." 23 | npx saddle compile $@ 24 | 25 | echo "$checksum" > "$cache_file" 26 | -------------------------------------------------------------------------------- /script/coverage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | dir=`dirname $0` 6 | proj_root="$dir/.." 7 | test_root="$dir/../tests" 8 | contracts_root="$dir/../contracts" 9 | coverage_root="$dir/../coverage" 10 | network=${NETWORK:-test} 11 | verbose=${verbose:-} 12 | 13 | # Compile scenario runner 14 | [[ -z $NO_TSC ]] && "$proj_root/scenario/script/tsc" 15 | 16 | # Build scenario stubs 17 | [[ -z $NO_BUILD_STUB ]] && "$proj_root/script/compile" --trace 18 | 19 | # Build scenario stubs 20 | [[ -z $NO_BUILD_SCEN ]] && "$proj_root/script/build_scenarios" 21 | 22 | rm -rf "$coverage_root" 23 | 24 | npx saddle coverage $@ 25 | coverage_code=$? 26 | 27 | npx istanbul report --root="$coverage_root" lcov json 28 | 29 | echo "Coverage generated. Report at $(cd $coverage_root && pwd)/lcov-report/index.html" 30 | 31 | exit $coverage_code 32 | -------------------------------------------------------------------------------- /script/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | DIR=`dirname $0` 6 | PROJ_ROOT="$DIR/.." 7 | echo "Running on tests..." 8 | "$PROJ_ROOT/node_modules/.bin/solium" -d "$PROJ_ROOT/tests" 9 | echo "Running on contracts..." 10 | "$PROJ_ROOT/node_modules/.bin/solium" -d "$PROJ_ROOT/contracts" 11 | -------------------------------------------------------------------------------- /script/saddle/comptrollerSize.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | (async () => { 4 | fs.readFile('.build/contracts-trace.json', (err, data) => { 5 | if (err) throw err; 6 | let contracts = JSON.parse(data); 7 | contracts = contracts["contracts"]; 8 | comptroller = contracts["contracts/Comptroller.sol:Comptroller"]; 9 | bin = comptroller["bin-runtime"] 10 | const digits = bin.length; 11 | const bytes = digits / 2; 12 | console.log("Current Comptroller compiles to", bytes, "bytes."); 13 | const limit = 24576; 14 | console.log("The EIP-170 limit is", limit); 15 | if (bytes <= limit) { 16 | console.log("You are fine by", limit - bytes, "bytes."); 17 | } else { 18 | console.log("Contract too big. You should reduce by", bytes - limit, "bytes"); 19 | } 20 | }); 21 | })(); 22 | -------------------------------------------------------------------------------- /script/saddle/deployToken.js: -------------------------------------------------------------------------------- 1 | let { loadConf } = require('./support/tokenConfig'); 2 | 3 | function printUsage() { 4 | console.log(` 5 | usage: npx saddle script token:deploy {tokenConfig} 6 | 7 | note: pass VERIFY=true and ETHERSCAN_API_KEY= to verify contract on Etherscan 8 | 9 | example: 10 | 11 | npx saddle -n rinkeby script token:deploy '{ 12 | "underlying": "0x577D296678535e4903D59A4C929B718e1D575e0A", 13 | "comptroller": "$Comptroller", 14 | "interestRateModel": "$Base200bps_Slope3000bps", 15 | "initialExchangeRateMantissa": "2.0e18", 16 | "name": "Compound Kyber Network Crystal", 17 | "symbol": "cKNC", 18 | "decimals": "8", 19 | "admin": "$Timelock" 20 | }' 21 | `); 22 | } 23 | 24 | function sleep(timeout) { 25 | return new Promise((resolve, reject) => { 26 | setTimeout(() => { 27 | resolve(); 28 | }, timeout); 29 | }); 30 | } 31 | 32 | (async function() { 33 | if (args.length !== 1) { 34 | return printUsage(); 35 | } 36 | 37 | let conf = loadConf(args[0], addresses); 38 | if (!conf) { 39 | return printUsage(); 40 | } 41 | 42 | console.log(`Deploying cToken with ${JSON.stringify(conf)}`); 43 | 44 | let deployArgs = [conf.underlying, conf.comptroller, conf.interestRateModel, conf.initialExchangeRateMantissa.toString(), conf.name, conf.symbol, conf.decimals, conf.admin]; 45 | let contract = await saddle.deploy('CErc20Immutable', deployArgs); 46 | 47 | console.log(`Deployed contract to ${contract._address}`); 48 | 49 | if (env['VERIFY']) { 50 | const etherscanApiKey = env['ETHERSCAN_API_KEY']; 51 | if (etherscanApiKey === undefined || etherscanApiKey.length === 0) { 52 | throw new Error(`ETHERSCAN_API_KEY must be set if using VERIFY flag...`); 53 | } 54 | 55 | console.log(`Sleeping for 30 seconds then verifying contract on Etherscan...`); 56 | await sleep(30000); // Give Etherscan time to learn about contract 57 | console.log(`Now verifying contract on Etherscan...`); 58 | 59 | await saddle.verify(etherscanApiKey, contract._address, 'CErc20Immutable', deployArgs, 0); 60 | console.log(`Contract verified at https://${network}.etherscan.io/address/${contract._address}`); 61 | } 62 | 63 | return { 64 | ...conf, 65 | address: contract._address 66 | }; 67 | })(); 68 | -------------------------------------------------------------------------------- /script/saddle/gasReport.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | (async () => { 4 | fs.readFile('gasCosts.json', (err, data) => { 5 | if (err) throw err; 6 | let gasReport = JSON.parse(data); 7 | 8 | console.log("Gas report") 9 | console.log("----------\n") 10 | 11 | for (const [scenario, report] of Object.entries(gasReport)) { 12 | cost = report.fee; 13 | console.log(scenario, "-", cost, "gas"); 14 | } 15 | }); 16 | })(); 17 | -------------------------------------------------------------------------------- /script/saddle/matchToken.js: -------------------------------------------------------------------------------- 1 | let { loadAddress, loadConf } = require('./support/tokenConfig'); 2 | 3 | function printUsage() { 4 | console.log(` 5 | usage: npx saddle script token:match address {tokenConfig} 6 | 7 | This checks to see if the deployed byte-code matches this version of the Compound Protocol. 8 | 9 | example: 10 | 11 | npx saddle -n rinkeby script token:match 0x19B674715cD20626415C738400FDd0d32D6809B6 '{ 12 | "underlying": "0x577D296678535e4903D59A4C929B718e1D575e0A", 13 | "comptroller": "$Comptroller", 14 | "interestRateModel": "$Base200bps_Slope3000bps", 15 | "initialExchangeRateMantissa": "2.0e18", 16 | "name": "Compound Kyber Network Crystal", 17 | "symbol": "cKNC", 18 | "decimals": "8", 19 | "admin": "$Timelock" 20 | }' 21 | `); 22 | } 23 | 24 | (async function() { 25 | if (args.length !== 2) { 26 | return printUsage(); 27 | } 28 | 29 | let address = loadAddress(args[0], addresses); 30 | let conf = loadConf(args[1], addresses); 31 | if (!conf) { 32 | return printUsage(); 33 | } 34 | 35 | console.log(`Matching cToken at ${address} with ${JSON.stringify(conf)}`); 36 | 37 | let deployArgs = [conf.underlying, conf.comptroller, conf.interestRateModel, conf.initialExchangeRateMantissa.toString(), conf.name, conf.symbol, conf.decimals, conf.admin]; 38 | 39 | await saddle.match(address, 'CErc20Immutable', deployArgs); 40 | 41 | return { 42 | ...conf, 43 | address 44 | }; 45 | })(); 46 | -------------------------------------------------------------------------------- /script/saddle/verifyToken.js: -------------------------------------------------------------------------------- 1 | let { loadAddress, loadConf } = require('./support/tokenConfig'); 2 | 3 | function printUsage() { 4 | console.log(` 5 | usage: npx saddle script token:verify {tokenAddress} {tokenConfig} 6 | 7 | note: $ETHERSCAN_API_KEY environment variable must be set to an Etherscan API Key. 8 | 9 | example: 10 | 11 | npx saddle -n rinkeby script token:verify 0x19B674715cD20626415C738400FDd0d32D6809B6 '{ 12 | "underlying": "0x577D296678535e4903D59A4C929B718e1D575e0A", 13 | "comptroller": "$Comptroller", 14 | "interestRateModel": "$Base200bps_Slope3000bps", 15 | "initialExchangeRateMantissa": "2.0e18", 16 | "name": "Compound Kyber Network Crystal", 17 | "symbol": "cKNC", 18 | "decimals": "8", 19 | "admin": "$Timelock" 20 | }' 21 | `); 22 | } 23 | 24 | (async function() { 25 | if (args.length !== 2) { 26 | return printUsage(); 27 | } 28 | 29 | let address = loadAddress(args[0], addresses); 30 | let conf = loadConf(args[1], addresses); 31 | if (!conf) { 32 | return printUsage(); 33 | } 34 | let etherscanApiKey = env['ETHERSCAN_API_KEY']; 35 | if (!etherscanApiKey) { 36 | console.error("Missing required $ETHERSCAN_API_KEY env variable."); 37 | return printUsage(); 38 | } 39 | 40 | console.log(`Verifying cToken at ${address} with ${JSON.stringify(conf)}`); 41 | 42 | let deployArgs = [conf.underlying, conf.comptroller, conf.interestRateModel, conf.initialExchangeRateMantissa.toString(), conf.name, conf.symbol, conf.decimals, conf.admin]; 43 | 44 | // TODO: Make sure we match optimizations count, etc 45 | await saddle.verify(etherscanApiKey, address, 'CErc20Immutable', deployArgs, 200, undefined); 46 | 47 | console.log(`Contract verified at https://${network}.etherscan.io/address/${address}`); 48 | 49 | return { 50 | ...conf, 51 | address 52 | }; 53 | })(); 54 | -------------------------------------------------------------------------------- /script/scen/deploy.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn run repl -s 2 | -- Deploys new Comptroller with some ERC20 and some cTokens 3 | 4 | -- First deploy a price oracle 5 | Gate (PriceOracle Address) (PriceOracle Deploy Simple) 6 | 7 | -- Next a comptroller 8 | Gate (Comptroller Address) (Comptroller Deploy YesNo) 9 | 10 | -- Next an interest rate model 11 | Gate (InterestRateModel InterestRateModel Address) (InterestRateModel Deploy Fixed InterestRateModel 0.0004) 12 | 13 | -- Now deploy some ERC-20 faucet tokens 14 | Gate (Erc20 ZRX Address) (Erc20 Deploy Standard ZRX "0x") 15 | Gate (Erc20 BAT Address) (Erc20 Deploy NonStandard BAT "Basic Attention Token") 16 | Gate (Erc20 DAI Address) (Erc20 Deploy Standard DAI "Dai") 17 | Gate (Erc20 REP Address) (Erc20 Deploy Standard REP "Augur") 18 | Gate (Erc20 USDC Address) (Erc20 Deploy Standard USDC "USD Coin" 6) 19 | 20 | -- Now deploy our cTokens 21 | Gate (CToken cZRX Address) (CToken Deploy CErc20 cZRX "Test 0x 📈" (Erc20 ZRX Address) (Comptroller Address) (InterestRateModel InterestRateModel Address) 0.2e9 8) 22 | Gate (CToken cBAT Address) (CToken Deploy CErc20 cBAT "Test Basic Attention Token 📈" (Erc20 BAT Address) (Comptroller Address) (InterestRateModel InterestRateModel Address) 0.2e9 8) 23 | Gate (CToken cDAI Address) (CToken Deploy CErc20 cDAI "Test Dai 📈" (Erc20 DAI Address) (Comptroller Address) (InterestRateModel InterestRateModel Address) 0.2e9 8) 24 | Gate (CToken cREP Address) (CToken Deploy CErc20 cREP "Test Augur 📈" (Erc20 REP Address) (Comptroller Address) (InterestRateModel InterestRateModel Address) 0.2e9 8) 25 | Gate (CToken cRBTC Address) (CToken Deploy CRBTC cRBTC "Test Ether 📈" (Comptroller Address) (InterestRateModel InterestRateModel Address) 0.2e9 8) 26 | Gate (CToken cUSDC Address) (CToken Deploy CErc20 cUSDC "Test USD Coin 📈" (Erc20 USDC Address) (Comptroller Address) (InterestRateModel InterestRateModel Address) 2e-4 8) 27 | 28 | -- Deploy Maximillion 29 | Gate (Maximillion Address) (Maximillion Deploy cRBTC) 30 | 31 | Print "Deployed Comptroller and cTokens: cRBTC, cBAT, cDAI, cREP, cUSDC and cZRX" 32 | -------------------------------------------------------------------------------- /script/scen/scriptFlywheel.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn run repl -s 2 | -- Deploys basic ComptrollerG3 3 | 4 | Unitroller Deploy 5 | PriceOracle Deploy Fixed 1.0 6 | PriceOracleProxy Deploy Admin (PriceOracle Address) (Address Zero) (Address Zero) (Address Zero) (Address Zero) (Address Zero) 7 | ----g2 8 | ComptrollerImpl Deploy ScenarioG2 ComptrollerScenG2 9 | Unitroller SetPendingImpl ComptrollerScenG2 10 | ComptrollerImpl ComptrollerScenG2 BecomeG2 11 | --list some tokens 12 | Comptroller SetPriceOracle (PriceOracleProxy Address) 13 | Comptroller SetMaxAssets 20 14 | Comptroller SetCloseFactor 0.5 15 | Comptroller LiquidationIncentive 1.1 16 | NewCToken ZRX cZRX 17 | NewCToken BAT cBAT 18 | Support cZRX collateralFactor:0.5 19 | Support cBAT collateralFactor:0.5 20 | -- final 21 | ComptrollerImpl Deploy Scenario ComptrollerScen 22 | Unitroller SetPendingImpl ComptrollerScen 23 | 24 | Prep Geoff 100e18 ZRX cZRX 25 | Mint Geoff 50e18 cZRX--tokenbalance = 50e18 / 2e9 = 2.5e10 26 | 27 | Prep Fourth Some BAT cBAT 28 | Mint Fourth 6e18 cBAT 29 | EnterMarkets Fourth cBAT 30 | Borrow Fourth 1e18 cZRX 31 | 32 | Prep Fifth Some BAT cBAT 33 | Mint Fifth 6e18 cBAT 34 | EnterMarkets Fifth cBAT 35 | Borrow Fifth 1e18 cZRX 36 | 37 | Prep Sixth Some BAT cBAT 38 | Mint Sixth 6e18 cBAT 39 | EnterMarkets Sixth cBAT 40 | Borrow Sixth 1e18 cZRX 41 | 42 | Prep Seventh Some BAT cBAT 43 | Mint Seventh 6e18 cBAT 44 | EnterMarkets Seventh cBAT 45 | Borrow Seventh 1e18 cZRX 46 | 47 | ComptrollerImpl ComptrollerScen Become 1e18 [cZRX cBAT] 48 | Erc20 Deploy Standard COMP "COMP Token" 18 49 | Give (Address Comptroller) 5000000e18 COMP 50 | Comptroller Send "setCompAddress(address)" (Address COMP) 51 | 52 | Comptroller RefreshCompSpeeds 53 | 54 | FastForward 300000 Blocks 55 | Read (Comptroller Address) 56 | Read (Address Fourth) 57 | Read (Address Fifth) 58 | Read (Address Sixth) 59 | Read (Address Seventh) 60 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | dir=`dirname $0` 6 | proj_root="$dir/.." 7 | network=${NETWORK:-test} 8 | verbose=${verbose:-} 9 | 10 | debug_args="-n --inspect" #add "debugger" statements to javascript and interact with running code in repl found at chrome://inspect 11 | [[ -z $DEBUG ]] && debug_args="" 12 | 13 | # Compile scenario runner 14 | [[ -z $NO_TSC ]] && "$proj_root/scenario/script/tsc" 15 | 16 | # Build scenario stubs 17 | [[ -z $NO_BUILD_STUB ]] && "$proj_root/script/compile" 18 | 19 | # Build scenario stubs 20 | [[ -z $NO_BUILD_SCEN ]] && "$proj_root/script/build_scenarios" 21 | 22 | [[ -n $NO_RUN ]] && exit 0 23 | 24 | args=() 25 | for arg in "$@"; do 26 | mapped=`node -e "console.log(\"$arg\".replace(/spec\/scenario\/(.*).scen/i, \"tests/Scenarios/\\$1ScenTest.js\"))"` 27 | args+=("$mapped") 28 | done 29 | 30 | proj_root="$proj_root" verbose="$verbose" npx $debug_args saddle test "${args[@]}" 31 | -------------------------------------------------------------------------------- /spec/scenario/BorrowEth.scen: -------------------------------------------------------------------------------- 1 | 2 | Test "Borrow some Eth enters Eth and succeeds when Eth not entered" 3 | NewComptroller price:1.0 4 | ListedCToken ZRX cZRX 5 | ListedEtherToken cRBTC initialExchangeRate:0.005e9 6 | SetCollateralFactor cZRX collateralFactor:0.5 7 | SetCollateralFactor cRBTC collateralFactor:0.5 8 | Donate cRBTC 0.003e18 9 | Prep Geoff Some ZRX cZRX 10 | Mint Geoff 1e18 cZRX 11 | EnterMarkets Geoff cZRX 12 | Expect Changes (EtherBalance Geoff) +0.001e18 13 | BorrowEth Geoff 0.001e18 cRBTC 14 | Assert Equal (EtherBalance cRBTC) 0.002e18 15 | Assert Equal (Comptroller Liquidity Geoff) 4.99e17 16 | Assert Equal (Comptroller MembershipLength Geoff) (Exactly 2) 17 | Assert True (Comptroller CheckMembership Geoff cRBTC) 18 | 19 | Test "Borrow some ETH fails when no ETH available" 20 | NewComptroller price:1.0 21 | ListedCToken ZRX cZRX 22 | ListedEtherToken cRBTC initialExchangeRate:0.005e9 23 | SetCollateralFactor cZRX collateralFactor:0.5 24 | SetCollateralFactor cRBTC collateralFactor:0.5 25 | Prep Geoff Some ZRX cZRX 26 | Mint Geoff 100e18 cZRX 27 | EnterMarkets Geoff cZRX cRBTC 28 | AllowFailures 29 | Invariant Static (CToken cZRX ExchangeRateStored) 30 | Invariant Static (CToken cRBTC ExchangeRateStored) 31 | Invariant Static (Comptroller Liquidity Geoff) 32 | Invariant Static (EtherBalance Geoff) 33 | BorrowEth Geoff 1e18 cRBTC 34 | Assert Failure TOKEN_INSUFFICIENT_CASH BORROW_CASH_NOT_AVAILABLE 35 | 36 | Test "Borrow some ETH from excess cash" 37 | NewComptroller price:1.0 38 | ListedCToken ZRX cZRX 39 | ListedEtherToken cRBTC initialExchangeRate:0.005e9 40 | SetCollateralFactor cZRX collateralFactor:0.5 41 | SetCollateralFactor cRBTC collateralFactor:0.5 42 | Donate cRBTC 0.003e18 43 | Prep Geoff Some ZRX cZRX 44 | Mint Geoff 1e18 cZRX 45 | EnterMarkets Geoff cZRX cRBTC 46 | Expect Changes (EtherBalance Geoff) +0.001e18 47 | BorrowEth Geoff 0.001e18 cRBTC 48 | Assert Equal (EtherBalance cRBTC) 0.002e18 49 | Assert Equal (Comptroller Liquidity Geoff) 4.99e17 50 | -------------------------------------------------------------------------------- /spec/scenario/BorrowWBTC.scen: -------------------------------------------------------------------------------- 1 | 2 | Test "Borrow some WBTC enters WBTC and succeeds when not entered" 3 | Invariant Success 4 | NewComptroller price:1.0 5 | NewCToken ZRX cZRX 6 | NewCToken WBTC cWBTC tokenType:WBTC 7 | Give cWBTC 10e8 WBTC -- Faucet some WBTC to borrow 8 | Support cZRX collateralFactor:0.5 9 | Support cWBTC collateralFactor:0.5 10 | Prep Geoff Some ZRX cZRX 11 | Mint Geoff 100e18 cZRX 12 | EnterMarkets Geoff cZRX 13 | Borrow Geoff 1e8 cWBTC 14 | Assert Equal (cToken cWBTC BorrowBalance Geoff) (Exactly 1e8) 15 | Assert Equal (Erc20 WBTC TokenBalance Geoff) (Exactly 1e8) 16 | Assert Equal (Erc20 WBTC TokenBalance cWBTC) (Exactly 9e8) 17 | 18 | Test "Borrow some WBTC fails when no WBTC available" 19 | NewComptroller price:1.0 20 | NewCToken ZRX cZRX 21 | NewCToken WBTC cWBTC tokenType:WBTC 22 | Support cZRX collateralFactor:0.5 23 | Support cWBTC collateralFactor:0.5 24 | Prep Geoff Some ZRX cZRX 25 | Mint Geoff 100e18 cZRX 26 | EnterMarkets Geoff cZRX cWBTC 27 | Invariant Static (CToken cZRX ExchangeRateStored) 28 | AllowFailures 29 | Borrow Geoff 1e8 cWBTC 30 | Assert Failure TOKEN_INSUFFICIENT_CASH BORROW_CASH_NOT_AVAILABLE 31 | 32 | Test "Borrow some WBTC fails when WBTC paused" 33 | NewComptroller price:1.0 34 | NewCToken ZRX cZRX 35 | NewCToken WBTC cWBTC tokenType:WBTC 36 | Give cWBTC 10e8 WBTC -- Faucet some WBTC to borrow 37 | Support cZRX collateralFactor:0.5 38 | Support cWBTC collateralFactor:0.5 39 | Prep Geoff Some ZRX cZRX 40 | Mint Geoff 100e18 cZRX 41 | EnterMarkets Geoff cZRX cWBTC 42 | Invariant Static (CToken cZRX ExchangeRateStored) 43 | Erc20 WBTC Pause 44 | AllowFailures 45 | Borrow Geoff 1e8 cWBTC 46 | Assert Revert 47 | 48 | Test "Borrow some WBTC from Excess Cash" 49 | Invariant Success 50 | NewComptroller price:1.0 51 | NewCToken ZRX cZRX 52 | NewCToken WBTC cWBTC tokenType:WBTC 53 | Give cWBTC 10e8 WBTC -- Faucet some WBTC to borrow 54 | Support cZRX collateralFactor:0.5 55 | Support cWBTC collateralFactor:0.5 56 | Prep Geoff Some ZRX cZRX 57 | Mint Geoff 100e18 cZRX 58 | EnterMarkets Geoff cZRX cWBTC 59 | Borrow Geoff 1e8 cWBTC 60 | EnterMarkets Geoff cZRX cWBTC 61 | Assert Equal (cToken cWBTC BorrowBalance Geoff) (Exactly 1e8) 62 | Assert Equal (Erc20 WBTC TokenBalance Geoff) (Exactly 1e8) 63 | Assert Equal (Erc20 WBTC TokenBalance cWBTC) (Exactly 9e8) 64 | -------------------------------------------------------------------------------- /spec/scenario/CTokenAdmin.scen: -------------------------------------------------------------------------------- 1 | 2 | Test "Set admin" 3 | NewComptroller 4 | NewCToken ZRX cZRX 5 | Assert Equal (CToken cZRX Admin) (Address Root) 6 | Assert Equal (CToken cZRX PendingAdmin) (Address Zero) 7 | From Root (CToken cZRX SetPendingAdmin Geoff) 8 | Assert Equal (CToken cZRX Admin) (Address Root) 9 | Assert Equal (CToken cZRX PendingAdmin) (Address Geoff) 10 | From Geoff (CToken cZRX AcceptAdmin) 11 | Assert Equal (CToken cZRX Admin) (Address Geoff) 12 | Assert Equal (CToken cZRX PendingAdmin) (Address Zero) 13 | 14 | Test "Set admin to contructor argument" 15 | NewComptroller 16 | NewCToken ZRX cZRX admin:Torrey 17 | Assert Equal (CToken cZRX Admin) (Address Torrey) 18 | Assert Equal (CToken cZRX PendingAdmin) (Address Zero) 19 | From Torrey (CToken cZRX SetPendingAdmin Geoff) 20 | Assert Equal (CToken cZRX Admin) (Address Torrey) 21 | Assert Equal (CToken cZRX PendingAdmin) (Address Geoff) 22 | From Geoff (CToken cZRX AcceptAdmin) 23 | Assert Equal (CToken cZRX Admin) (Address Geoff) 24 | Assert Equal (CToken cZRX PendingAdmin) (Address Zero) 25 | 26 | 27 | Test "Fail to set pending admin" 28 | NewComptroller 29 | NewCToken ZRX cZRX 30 | Invariant Remains (CToken cZRX Admin) (Address Root) 31 | Invariant Remains (CToken cZRX PendingAdmin) (Address Zero) 32 | AllowFailures 33 | From Geoff (CToken cZRX SetPendingAdmin Geoff) 34 | Assert Failure UNAUTHORIZED SET_PENDING_ADMIN_OWNER_CHECK 35 | 36 | Test "Fail to accept admin" 37 | NewComptroller 38 | NewCToken ZRX cZRX 39 | Invariant Remains (CToken cZRX Admin) (Address Root) 40 | Invariant Remains (CToken cZRX PendingAdmin) (Address Zero) 41 | AllowFailures 42 | From Geoff (CToken cZRX AcceptAdmin) 43 | Assert Failure UNAUTHORIZED ACCEPT_ADMIN_PENDING_ADMIN_CHECK 44 | -------------------------------------------------------------------------------- /spec/scenario/ChangeDelegate.scen: -------------------------------------------------------------------------------- 1 | -- Delegate upgrade tests 2 | 3 | Test "Change the delegate" 4 | NewComptroller 5 | NewCToken DEL cDEL 6 | Support cDEL collateralFactor:0.5 7 | Prep Jared Some DEL cDEL 8 | Mint Jared 100e18 cDEL 9 | CTokenDelegate Deploy CErc20Delegate cErc20Delegate2 10 | CToken cDEL SetImplementation (CTokenDelegate cErc20Delegate2 Address) True "0x0" 11 | Redeem Jared 50e9 cDEL 12 | -------------------------------------------------------------------------------- /spec/scenario/Governor/Defeat.scen: -------------------------------------------------------------------------------- 1 | Macro DeployGov 2 | SetBlockNumber 1 3 | Counter Deploy CNT1 4 | Timelock Deploy Scenario Jared 604800 5 | TROP Deploy Bank 6 | Governor Deploy Alpha LegitGov (Address Timelock) (Address TROP) Guardian 7 | Timelock SetAdmin (Address LegitGov) 8 | Enfranchise Root 100001e18 9 | Enfranchise Jared 200000e18 10 | Enfranchise Torrey 600001e18 11 | Enfranchise Geoff 700001e18 12 | 13 | Macro Enfranchise user amount 14 | From Bank (TROP Transfer user amount) 15 | From user (TROP Delegate user) 16 | 17 | Macro GivenPendingProposal 18 | DeployGov 19 | MineBlock 20 | MineBlock 21 | Governor LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]] 22 | Assert Equal ("Pending") (Governor LegitGov Proposal LastProposal State) 23 | 24 | Macro GivenActiveProposal 25 | GivenPendingProposal 26 | MineBlock 27 | MineBlock 28 | Assert Equal ("Active") (Governor LegitGov Proposal LastProposal State) 29 | 30 | Test "Defeat when for votes do not reach quorum" 31 | GivenActiveProposal 32 | Governor LegitGov Proposal LastProposal Vote For 33 | AdvanceBlocks 20000 34 | Assert Equal ("Defeated") (Governor LegitGov Proposal LastProposal State) 35 | 36 | Test "Defeat when more against votes than for votes" 37 | GivenActiveProposal 38 | From Torrey (Governor LegitGov Proposal LastProposal Vote For ) 39 | From Geoff (Governor LegitGov Proposal LastProposal Vote Against ) 40 | AdvanceBlocks 20000 41 | Assert Equal ("Defeated") (Governor LegitGov Proposal LastProposal State) 42 | 43 | Test "(not defeat) when vote is ongoing" 44 | GivenActiveProposal 45 | From Torrey (Governor LegitGov Proposal LastProposal Vote For ) 46 | From Geoff (Governor LegitGov Proposal LastProposal Vote For ) 47 | Assert Equal ("Active") (Governor LegitGov Proposal LastProposal State) 48 | 49 | Test "(not defeat) when fors pass quorum and nays" 50 | GivenActiveProposal 51 | From Torrey (Governor LegitGov Proposal LastProposal Vote For ) 52 | From Geoff (Governor LegitGov Proposal LastProposal Vote For ) 53 | From Jared (Governor LegitGov Proposal LastProposal Vote Against ) 54 | AdvanceBlocks 20000 55 | Assert Equal ("Succeeded") (Governor LegitGov Proposal LastProposal State) 56 | -------------------------------------------------------------------------------- /spec/scenario/PriceOracleProxy.scen: -------------------------------------------------------------------------------- 1 | Macro SetupPriceOracleProxy 2 | Unitroller Deploy 3 | PriceOracle Deploy Simple 4 | -- Update to G1 5 | ComptrollerImpl Deploy ScenarioG1 ScenComptrollerG1 6 | Unitroller SetPendingImpl ScenComptrollerG1 7 | PriceOracleProxy Deploy Admin (PriceOracle Address) (Address Zero) (Address Zero) (Address Zero) (Address Zero) (Address Zero) 8 | ComptrollerImpl ScenComptrollerG1 BecomeG1 (PriceOracleProxy Address) 0.1 20 9 | -- Update to G2 10 | ComptrollerImpl Deploy StandardG2 ComptrollerG2 11 | Unitroller SetPendingImpl ComptrollerG2 12 | ComptrollerImpl ComptrollerG2 BecomeG2 13 | -- Update to G3 14 | ComptrollerImpl Deploy StandardG3 ComptrollerG3 15 | Unitroller SetPendingImpl ComptrollerG3 16 | ComptrollerImpl ComptrollerG3 BecomeG3 1e18 [] 17 | -- Update to G* 18 | ComptrollerImpl Deploy Scenario ScenComptroller 19 | Unitroller SetPendingImpl ScenComptroller 20 | ComptrollerImpl ScenComptroller Become 21 | NewEtherToken cETH 22 | NewCToken USDC cUSDC 23 | NewCToken SAI cSAI 24 | NewCToken DAI cDAI 25 | NewCToken USDT cUSDT 26 | Comptroller SupportMarket cETH 27 | Comptroller SupportMarket cUSDC 28 | Comptroller SupportMarket cSAI 29 | Comptroller SupportMarket cDAI 30 | Comptroller SupportMarket cUSDT 31 | PriceOracleProxy Deploy Admin (PriceOracle Address) (Address cETH) (Address cUSDC) (Address cSAI) (Address cDAI) (Address cUSDT) 32 | Comptroller SetPriceOracle (PriceOracleProxy Address) 33 | 34 | Test "uses address(2) for dai and address(1) for usdc" 35 | SetupPriceOracleProxy 36 | PriceOracle SetDirectPrice (Address 0x0000000000000000000000000000000000000001) 5740564708.572881 37 | PriceOracle SetDirectPrice (Address 0x0000000000000000000000000000000000000002) 0.005842307360923634 38 | Assert Equal (PriceOracleProxy Price cUSDC) 5740564708572881000000000000 39 | Assert Equal (PriceOracleProxy Price cDAI) 5842307360923634 40 | 41 | Test "gets tether and usdc prices" 42 | SetupPriceOracleProxy 43 | PriceOracle SetDirectPrice (Address 0x0000000000000000000000000000000000000001) 5740564708.572881 44 | -- scaled to 1e30 bc both tokens have 6 decimals 45 | Assert Equal (PriceOracleProxy Price cUSDT) 5740564708572881000000000000 46 | Assert Equal (PriceOracleProxy Price cUSDC) 5740564708572881000000000000 47 | -------------------------------------------------------------------------------- /spec/scenario/ReEntry.scen: -------------------------------------------------------------------------------- 1 | 2 | Test "ReEntry Mint @no-cov" 3 | NewComptroller 4 | Erc20 Deploy ReEntrant PHREAK PHREAK "transferFrom" "mint(uint256)" "0" 5 | InterestRateModel Deploy Fixed Std 0.000001 6 | CToken Deploy Scenario cPHREAK cPHREAK (Erc20 PHREAK Address) (Comptroller Address) (InterestRateModel Std Address) 1e9 8 Admin 7 | Comptroller SupportMarket cPHREAK 8 | Prep Geoff Some PHREAK cPHREAK 9 | AllowFailures 10 | Mint Geoff 50e18 cPHREAK 11 | Assert Revert "revert T26" 12 | Assert Equal (Erc20 cPHREAK TokenBalance Geoff) Zero 13 | -------------------------------------------------------------------------------- /spec/scenario/Seize.scen: -------------------------------------------------------------------------------- 1 | 2 | Test "Fail to seize calling directly" 3 | NewComptroller 4 | ListedCToken ZRX cZRX initialExchangeRate:1e9 5 | ListedCToken BAT cBAT initialExchangeRate:1e9 6 | Prep Geoff Some ZRX cZRX 7 | Mint Geoff 50e18 cZRX 8 | Invariant Remains (Erc20 cZRX TokenBalance Geoff) 50e9 9 | AllowFailures 10 | Seize 1e9 cZRX caller:Geoff liquidator:Geoff borrower:Torrey 11 | -- The caller must be from another cToken market, thus this fails 12 | Assert Failure COMPTROLLER_REJECTION LIQUIDATE_SEIZE_COMPTROLLER_REJECTION MARKET_NOT_LISTED 13 | 14 | Test "Seize tokens with a paused WBTC cToken-- like normal" 15 | NewComptroller 16 | ListedCToken ZRX cZRX initialExchangeRate:1e9 17 | ListedCToken WBTC cWBTC initialExchangeRate:0.1 tokenType:WBTC 18 | Prep Geoff Some ZRX cZRX 19 | Mint Geoff 50e18 cZRX 20 | Erc20 WBTC Pause 21 | Invariant Remains (Erc20 cZRX TokenBalance Geoff) 50e9 22 | AllowFailures 23 | Seize 1e9 cWBTC caller:Geoff liquidator:Geoff borrower:Torrey 24 | -- The caller must be from another cToken market, thus this fails 25 | Assert Failure COMPTROLLER_REJECTION LIQUIDATE_SEIZE_COMPTROLLER_REJECTION MARKET_NOT_LISTED 26 | 27 | Test "Not able to seize tokens with a malicious unlisted cToken" 28 | NewComptroller 29 | ListedCToken ZRX cZRX initialExchangeRate:1e9 30 | NewCTokenImmutable EVL cEVL initialExchangeRate:1e9 cTokenType:CEvil 31 | Prep Geoff Some ZRX cZRX 32 | Mint Geoff 50e18 cZRX 33 | Invariant Remains (Erc20 cZRX TokenBalance Geoff) 50e9 34 | Invariant Static (Erc20 cZRX TokenBalance Geoff) 35 | Invariant Static (Erc20 cZRX TokenBalance Torrey) 36 | AllowFailures 37 | EvilSeize cEVL 1e9 cZRX seizer:Geoff seizee:Torrey 38 | -- The caller must be from another cToken market, thus this fails 39 | Assert Failure COMPTROLLER_REJECTION LIQUIDATE_SEIZE_COMPTROLLER_REJECTION MARKET_NOT_LISTED 40 | 41 | Test "Able to seize tokens with a malicious listed cToken" 42 | NewComptroller 43 | ListedCToken ZRX cZRX initialExchangeRate:1e9 44 | ListedCTokenImmutable EVL cEVL initialExchangeRate:1e9 cTokenType:CEvil 45 | Prep Geoff Some ZRX cZRX 46 | Mint Geoff 50e18 cZRX 47 | Assert Equal (Erc20 cZRX TokenBalance Geoff) 50e9 48 | Expect Changes (Erc20 cZRX TokenBalance Geoff) -1e9 49 | Expect Changes (Erc20 cZRX TokenBalance Torrey) +1e9 50 | EvilSeize cEVL 1e9 cZRX seizer:Torrey seizee:Geoff 51 | -------------------------------------------------------------------------------- /spec/scenario/SetComptroller.scen: -------------------------------------------------------------------------------- 1 | -- Sets for `_setComptroller` Admin Function 2 | 3 | Test "Set Comptroller" 4 | NewComptroller 5 | NewCToken ZRX cZRX 6 | Assert Equal (CToken cZRX Comptroller) (Unitroller Address) 7 | ComptrollerImpl Deploy Scenario NewComptroller 8 | From Root (CToken cZRX SetComptroller (ComptrollerImpl NewComptroller Address)) 9 | -- TODO: Fix log assertion 10 | -- Assert Log "NewComptroller" ("oldComptroller" (Unitroller Address)) ("newComptroller" (ComptrollerImpl NewComptroller Address)) 11 | Assert Equal (CToken cZRX Comptroller) (ComptrollerImpl NewComptroller Address) 12 | 13 | Test "Fail when is not a comptroller" 14 | NewComptroller 15 | NewCToken ZRX cZRX 16 | Invariant Remains (CToken cZRX Comptroller) (Unitroller Address) 17 | AllowFailures 18 | From Root (CToken cZRX SetComptroller (PriceOracle Address)) 19 | Assert Revert 20 | 21 | Test "Fail to set comptroller as not admin" 22 | NewComptroller 23 | NewCToken ZRX cZRX 24 | AllowFailures 25 | From Geoff (CToken cZRX SetComptroller (PriceOracle Address)) 26 | Assert Failure UNAUTHORIZED SET_COMPTROLLER_OWNER_CHECK 27 | -------------------------------------------------------------------------------- /spec/scenario/Supply.scen.old: -------------------------------------------------------------------------------- 1 | -- Supply Tests 2 | 3 | Test "Geoff supplies Ether and we check 2 future balances and then supply again" 4 | AddToken Ether 5 | SupportMarket Ether (FixedPrice 1.0) (FixedRate 0.5 0.75) SimplePolicyHook 6 | Approve Geoff Ether "10.0e18" 7 | Faucet Geoff Ether "10.0e18" 8 | Supply Geoff Ether "3e18" 9 | Assert Success 10 | FastForward 2 Blocks 11 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "6.0e18") -- 3 * ( 1 + 2 * .5 ) 12 | FastForward 2 Blocks 13 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "9.0e18") -- 3 * ( 1 + 4 * .5 ) 14 | Supply Geoff Ether "1e18" 15 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "10.0e18") -- 3 * ( 1 + 4 * .5 ) + 1 16 | FastForward 2 Blocks 17 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "20.0e18") -- 10 * ( 1 + 2 * .5 ) 18 | 19 | Test "Geoff supplies Ether, Torrey supplies Ether and then Geoff supplies more Ether" 20 | AddToken Ether 21 | SupportMarket Ether (FixedPrice 1.0) (FixedRate 0.5 0.75) SimplePolicyHook 22 | Approve Geoff Ether "10.0e18" 23 | Faucet Geoff Ether "10.0e18" 24 | Approve Torrey Ether "5.0e18" 25 | Faucet Torrey Ether "5.0e18" 26 | Supply Geoff Ether "1e18" 27 | Assert Success 28 | FastForward 2 Blocks 29 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "2.0e18") 30 | Supply Torrey Ether "3e18" 31 | Assert Success 32 | FastForward 2 Blocks 33 | Assert Equal (SupplyBalance Torrey Ether) (Exactly "6.0e18") 34 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "4.0e18") 35 | Supply Geoff Ether "1e18" 36 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "5.0e18") 37 | 38 | Test "Can't supply an 'initial' asset" 39 | AddToken Dragon 40 | Approve Geoff Dragon "10.0e18" 41 | Faucet Geoff Dragon "10.0e18" 42 | Supply Geoff Dragon "1e18" 43 | Assert Failure MARKET_NOT_LISTED SUPPLY_MARKET_NOT_LISTED 44 | 45 | Test "Can't supply when contract is paused" 46 | AddToken Ether 47 | SupportMarket Ether (FixedPrice 1.0) (FixedRate 0.5 0.75) SimplePolicyHook 48 | Approve Geoff Ether 1.0e18 49 | Faucet Geoff Ether 0.4e18 50 | PolicyHook Ether (SetPaused True) 51 | Supply Geoff Ether 0.3e18 52 | Assert Failure COMPTROLLER_REJECTION SUPPLY_COMPTROLLER_REJECTION 1 53 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "0e18") 54 | 55 | Test "With always paused policy hook, can't supply when contract is paused" 56 | AddToken Ether 57 | SupportMarket Ether (FixedPrice 1.0) (FixedRate 0.5 0.75) AlwaysPausedPolicyHook 58 | Supply Geoff Ether 0.3e18 59 | Assert Failure COMPTROLLER_REJECTION SUPPLY_COMPTROLLER_REJECTION 99 60 | -------------------------------------------------------------------------------- /spec/scenario/SweepToken.scen: -------------------------------------------------------------------------------- 1 | 2 | Test "Attempt to sweep underlying token" 3 | NewComptroller price:1.0 4 | NewCToken ZRX cZRX 5 | NewCToken BAT cBAT 6 | Give cBAT 10e18 BAT -- Faucet some bat to sweep 7 | Support cZRX collateralFactor:0.5 8 | Support cBAT collateralFactor:0.5 9 | AllowFailures 10 | cToken cBAT SweepToken BAT 11 | Assert Revert "revert E1" 12 | 13 | Test "Succesfully Sweep standard non-underlying token from CErc20" 14 | NewComptroller price:1.0 15 | Erc20 Deploy Standard ZRX "ZRX" 18 16 | NewCToken BAT cBAT 17 | Give cBAT 10e18 ZRX -- Faucet some ZRX to sweep 18 | Support cBAT collateralFactor:0.5 19 | cToken cBAT SweepToken ZRX 20 | Assert Equal (Erc20 ZRX TokenBalance Admin) (10e18) 21 | 22 | Test "Succesfully Sweep non-standard non-underlying token from CErc20" 23 | NewComptroller price:1.0 24 | Erc20 Deploy NonStandard USDT "USDT" 18 25 | NewCToken BAT cBAT 26 | Give cBAT 10e18 USDT -- Faucet some USDT to sweep 27 | Support cBAT collateralFactor:0.5 28 | cToken cBAT SweepToken USDT 29 | Assert Equal (Erc20 USDT TokenBalance Admin) (10e18) 30 | -------------------------------------------------------------------------------- /spec/scenario/Withdraw.scen.old: -------------------------------------------------------------------------------- 1 | -- Withdraw Tests 2 | 3 | Test "Supply Ether 5 then Withdraw MAX in the same block" 4 | AddToken Ether -- baseline sanity check for withdraw max 5 | SupportMarket Ether (FixedPrice 1.0) (FixedRate 0.5 0.75) SimplePolicyHook 6 | Approve Geoff Ether "6.0e18" 7 | Faucet Geoff Ether "6.0e18" 8 | Supply Geoff Ether "5.0e18" 9 | Assert Success 10 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "5.0e18") 11 | Assert Equal (BorrowBalance Geoff Ether) (Exactly "0e18") 12 | Assert Equal (MaxBorrow Geoff) (Exactly "2.5e18") 13 | Withdraw Geoff Ether "MAX" 14 | Assert Success 15 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "0.0e18") 16 | Assert Equal (TokenBalance Geoff Ether) (Exactly "6e18") 17 | 18 | Test "Supply Ether 5 then Withdraw MAX (6) after accruing some interest" 19 | AddToken Ether 20 | SupportMarket Ether (FixedPrice 1.0) (FixedRate 0.5 0.75) SimplePolicyHook 21 | Approve Geoff Ether "6.0e18" 22 | Faucet Geoff Ether "6.0e18" 23 | Supply Geoff Ether "5.0e18" -- We need more Ether in the system to simulate protocol gaining borrow interest to pay Geoff 24 | Approve Torrey Ether "10.0e18" 25 | Faucet Torrey Ether "10.0e18" 26 | Supply Torrey Ether "10.0e18" 27 | Assert Success 28 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "5.0e18") 29 | FastForward 2 Blocks 30 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "10.0e18") 31 | Withdraw Geoff Ether "MAX" 32 | Assert Success 33 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "0.0e18") 34 | Assert Equal (TokenBalance Geoff Ether) (Exactly "11e18") 35 | 36 | Test "Withdraw Ether 1 when contract paused" 37 | AddToken Ether 38 | SupportMarket Ether (FixedPrice 1.0) (FixedRate 0.5 0.75) SimplePolicyHook 39 | Approve Geoff Ether "1.0e18" 40 | Faucet Geoff Ether "1.0e18" 41 | Supply Geoff Ether "1.0e18" 42 | Assert Success 43 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "1.0e18") 44 | PolicyHook Ether (SetPaused True) 45 | Withdraw Geoff Ether "1.0e18" 46 | Assert Failure COMPTROLLER_REJECTION WITHDRAW_COMPTROLLER_REJECTION 1 47 | Assert Equal (SupplyBalance Geoff Ether) (Exactly "1e18") 48 | Assert Equal (TokenBalance Geoff Ether) (Exactly "0e18") 49 | -------------------------------------------------------------------------------- /spec/sim/0001-comp-distribution-patch/kovan/execute.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -n kovan -s 2 | 3 | PrintTransactionLogs 4 | 5 | -- Execute the proposal 6 | Governor GovernorAlpha Proposal 8 Execute 7 | 8 | Print "Executed OK!" -------------------------------------------------------------------------------- /spec/sim/0001-comp-distribution-patch/kovan/queue.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -n queue -s 2 | 3 | PrintTransactionLogs 4 | 5 | -- Queue the proposal 6 | Governor GovernorAlpha Proposal 8 Queue 7 | 8 | Print "Queued OK!" -------------------------------------------------------------------------------- /spec/sim/0001-comp-distribution-patch/ropsten/execute.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -n ropsten -s 2 | 3 | PrintTransactionLogs 4 | 5 | -- Execute the proposal 6 | Governor GovernorAlpha Proposal 9 Execute 7 | 8 | Print "Executed OK!" -------------------------------------------------------------------------------- /spec/sim/0001-comp-distribution-patch/ropsten/queue.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -n ropsten -s 2 | 3 | PrintTransactionLogs 4 | 5 | -- Queue the proposal 6 | Governor GovernorAlpha Proposal 9 Queue 7 | 8 | Print "Queued OK!" -------------------------------------------------------------------------------- /spec/sim/0003-borrow-caps-patch/deploy.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -n mainnet -s 2 | 3 | PrintTransactionLogs 4 | 5 | -- Deploy the flywheel impl 6 | ComptrollerImpl Deploy Standard StdComptrollerG5 7 | 8 | Print "Deployed OK!" 9 | -------------------------------------------------------------------------------- /spec/sim/0003-borrow-caps-patch/hypothetical_upgrade.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompHolder "0x19bc62ff7cd9ffd6bdced9802ff718f09f7259f1" 5 | Alias USDCWhale "0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3" 6 | Alias Arr00 "0x2b384212edc04ae8bb41738d05ba20e33277bf33" 7 | Web3Fork "https://mainnet-eth.compound.finance/@10706095" (CompHolder USDCWhale) 8 | UseConfigs mainnet 9 | 10 | -- Deploy the flywheel impl 11 | 12 | ComptrollerImpl Deploy Standard ComptrollerG5 13 | 14 | -- Propose to apply the patch 15 | 16 | From CompHolder (Comp Delegate CompHolder) 17 | From CompHolder (Governor GovernorAlpha Propose "Borrow Cap Comptroller Patch" [(Address Unitroller) (Address ComptrollerG5) (Address Unitroller) (Address cSAI) (Address SAI)] [0 0 0 0 0] ["_setPendingImplementation(address)" "_become(address)" "_setBorrowCapGuardian(address)" "_reduceReserves(uint256)" "transfer(address,uint256)"] [[(Address ComptrollerG5)] [(Address Unitroller)] [(Address CompHolder)] [2360000000000000000000] [(Address Arr00) 2360000000000000000000]]) 18 | 19 | -- Vote for, queue, and execute the proposal 20 | 21 | MineBlock 22 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 23 | AdvanceBlocks 20000 24 | Governor GovernorAlpha Proposal LastProposal Queue 25 | IncreaseTime 604910 26 | Governor GovernorAlpha Proposal LastProposal Execute 27 | ComptrollerImpl ComptrollerG5 MergeABI 28 | 29 | Assert Equal (Address (Unitroller Implementation)) (Address ComptrollerG5) 30 | Assert Equal (Erc20 SAI TokenBalance (Address Arr00)) (2360000000000000000000) 31 | 32 | From USDCWhale (Trx GasPrice 0 (Erc20 USDC Approve cUSDC UInt256Max)) 33 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Mint 214000000e6)) 34 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Borrow 1000000e6)) 35 | 36 | From CompHolder (Comptroller SetMarketBorrowCaps (cUSDC) (83000000e6)) 37 | 38 | AllowFailures 39 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Borrow 1000000e6)) 40 | Assert Revert 41 | 42 | Successfully 43 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC RepayBorrow 1000000e6)) 44 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Borrow 10e6)) 45 | 46 | 47 | 48 | Print "Borrow Cap Comptroller Patch OK!" -------------------------------------------------------------------------------- /spec/sim/0003-borrow-caps-patch/hypothetical_upgrade_post_deploy.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompHolder "0x19bc62ff7cd9ffd6bdced9802ff718f09f7259f1" 5 | Alias USDCWhale "0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3" 6 | Alias Arr00 "0x2b384212edc04ae8bb41738d05ba20e33277bf33" 7 | Web3Fork "https://mainnet-eth.compound.finance/@10809638" (CompHolder USDCWhale) 8 | UseConfigs mainnet 9 | 10 | 11 | -- Propose to apply the patch 12 | 13 | From CompHolder (Comp Delegate CompHolder) 14 | From CompHolder (Governor GovernorAlpha Propose "Borrow Limit Comptroller Patch" [(Address Unitroller) (Address StdComptrollerG5) (Address Unitroller) (Address cSAI) (Address SAI)] [0 0 0 0 0] ["_setPendingImplementation(address)" "_become(address)" "_setBorrowCapGuardian(address)" "_reduceReserves(uint256)" "transfer(address,uint256)"] [[(Address StdComptrollerG5)] [(Address Unitroller)] [(Address CompHolder)] [2360000000000000000000] [(Address Arr00) 2360000000000000000000]]) 15 | 16 | -- Vote for, queue, and execute the proposal 17 | 18 | MineBlock 19 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 20 | AdvanceBlocks 20000 21 | Governor GovernorAlpha Proposal LastProposal Queue 22 | IncreaseTime 604910 23 | Governor GovernorAlpha Proposal LastProposal Execute 24 | ComptrollerImpl StdComptrollerG5 MergeABI 25 | 26 | Assert Equal (Address (Unitroller Implementation)) (Address StdComptrollerG5) 27 | Assert Equal (Erc20 SAI TokenBalance (Address Arr00)) (2360000000000000000000) 28 | 29 | From USDCWhale (Trx GasPrice 0 (Erc20 USDC Approve cUSDC UInt256Max)) 30 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Mint 100000000e6)) 31 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Borrow 1000000e6)) 32 | 33 | -- Market borrows were just under 81M at this block 34 | From CompHolder (Comptroller SetMarketBorrowCaps (cUSDC) (68000000e6)) 35 | 36 | AllowFailures 37 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Borrow 1000000e6)) 38 | Assert Revert 39 | 40 | Successfully 41 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC RepayBorrow 1000000e6)) 42 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Borrow 10e6)) 43 | 44 | 45 | 46 | Print "Borrow Limit Comptroller Patch OK!" -------------------------------------------------------------------------------- /spec/sim/0003-borrow-caps-patch/hypothetical_upgrade_post_propose.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompVoter1 "0x9aa835bc7b8ce13b9b0c9764a52fbf71ac62ccf1" 5 | Alias CompVoter2 "0xed409c9ff60f3020abf9012bcd45fc294f5608ff" 6 | Alias USDCWhale "0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3" 7 | Alias Arr00 "0x2b384212edc04ae8bb41738d05ba20e33277bf33" 8 | Web3Fork "https://mainnet-eth.compound.finance/@10823813" (USDCWhale CompVoter1 CompVoter2) 9 | UseConfigs mainnet 10 | 11 | 12 | 13 | -- Vote for, queue, and execute the proposal 14 | 15 | MineBlock 16 | From CompVoter1 (Governor GovernorAlpha Proposal LastProposal Vote For) 17 | From CompVoter2 (Governor GovernorAlpha Proposal LastProposal Vote For) 18 | AdvanceBlocks 20000 19 | Governor GovernorAlpha Proposal LastProposal Queue 20 | IncreaseTime 604910 21 | Governor GovernorAlpha Proposal LastProposal Execute 22 | ComptrollerImpl StdComptrollerG5 MergeABI 23 | 24 | Assert Equal (Address (Unitroller Implementation)) (Address StdComptrollerG5) 25 | Assert Equal (Erc20 SAI TokenBalance (Address Arr00)) (2800000000000000000000) 26 | 27 | From USDCWhale (Trx GasPrice 0 (Erc20 USDC Approve cUSDC UInt256Max)) 28 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Mint 100000000e6)) 29 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Borrow 1000000e6)) 30 | 31 | 32 | Print "Borrow Limit Comptroller Patch OK!" -------------------------------------------------------------------------------- /spec/sim/0004-cusdc-irm-update/hypothetical_upgrade.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompHolder "0x19bc62ff7cd9ffd6bdced9802ff718f09f7259f1" 5 | Web3Fork "https://mainnet-eth.compound.finance/@10796407" (CompHolder) 6 | UseConfigs mainnet 7 | 8 | -- Deploy new interest rate model 9 | InterestRateModel Deploy LegacyJumpRateModelV2 MyInterestRateModel 0 0.04 1.09 0.8 (Address Timelock) 10 | 11 | -- Propose to apply the patch 12 | 13 | From CompHolder (Comp Delegate CompHolder) 14 | From CompHolder (Governor GovernorAlpha Propose "Update cUSDC interest rate model" [(Address cUSDC)] [0] ["_setInterestRateModel(address)"] [[(Address MyInterestRateModel)]]) 15 | 16 | -- Vote for, queue, and execute the proposal 17 | 18 | MineBlock 19 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 20 | AdvanceBlocks 20000 21 | Governor GovernorAlpha Proposal LastProposal Queue 22 | IncreaseTime 604910 23 | Governor GovernorAlpha Proposal LastProposal Execute 24 | 25 | 26 | Assert Equal (Address (CToken cUSDC InterestRateModel)) (Address MyInterestRateModel) 27 | CToken cUSDC AccrueInterest 28 | 29 | 30 | Print "cUSDC interest rate model upgrade ok" -------------------------------------------------------------------------------- /spec/sim/0005-grants/deploy.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -n mainnet -s 2 | 3 | PrintTransactionLogs 4 | 5 | -- Deploy the flywheel impl 6 | ComptrollerImpl Deploy Standard StdComptrollerG6 7 | 8 | Print "Deployed OK!" 9 | -------------------------------------------------------------------------------- /spec/sim/0005-grants/hypothetical_upgrade_post_propose.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | -- This script tests a hypothetical upgrade with one time and streaming grants 4 | 5 | PrintTransactionLogs 6 | Alias CompHolder "0x19bc62ff7cd9ffd6bdced9802ff718f09f7259f1" 7 | Alias CompVoter1 "0x9aa835bc7b8ce13b9b0c9764a52fbf71ac62ccf1" 8 | Alias CompVoter2 "0xed409c9ff60f3020abf9012bcd45fc294f5608ff" 9 | Alias USDCWhale "0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3" 10 | Alias Tarun "0x6626593c237f530d15ae9980a95ef938ac15c35c" 11 | Web3Fork "https://mainnet-eth.compound.finance/@11395519" (CompHolder USDCWhale Tarun CompVoter1 CompVoter2) 12 | UseConfigs mainnet 13 | 14 | Assert Equal (Erc20 COMP TokenBalance (Address Tarun)) (2e18) 15 | 16 | -- Vote for, queue, and execute the proposal 17 | 18 | MineBlock 19 | From CompVoter1 (Governor GovernorAlpha Proposal LastProposal Vote For) 20 | From CompVoter2 (Governor GovernorAlpha Proposal LastProposal Vote For) 21 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 22 | AdvanceBlocks 20000 23 | Governor GovernorAlpha Proposal LastProposal Queue 24 | IncreaseTime 604910 25 | Governor GovernorAlpha Proposal LastProposal Execute 26 | ComptrollerImpl ComptrollerG6 MergeABI 27 | 28 | -- Check grant amount was apportioned 29 | Assert Equal (Erc20 COMP TokenBalance (Address Tarun)) (1002e18) 30 | 31 | Print "Upgrade OK!" 32 | -------------------------------------------------------------------------------- /spec/sim/0006-setspeed-manual-claims/deploy.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -n mainnet -s 2 | 3 | PrintTransactionLogs 4 | 5 | -- Deploy the flywheel impl 6 | ComptrollerImpl Deploy Standard StdComptrollerG7 7 | 8 | Print "Deployed OK!" 9 | -------------------------------------------------------------------------------- /spec/sim/0006-setspeed-manual-claims/hypothetical_upgrade_borrower.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | -- This script tests a hypothetical upgrade with manual COMP claims and manual COMP speed setting 4 | 5 | PrintTransactionLogs 6 | Alias CompHolder "0xC89b6f0146642688bb254bF93C28fcCF1E182C81" 7 | Alias USDCWhale "0x3dfd23a6c5e8bbcfc9581d2e864a68feb6a076d3" 8 | Alias DAIWhale "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503" 9 | Alias BigBorrower "0x847956f7f7ff49714fb2d70a0d0cd44a6376990f" 10 | Web3Fork "https://mainnet-eth.compound.finance/@11458477" (CompHolder USDCWhale DAIWhale) 11 | UseConfigs mainnet 12 | 13 | -- Deploy the flywheel impl 14 | ComptrollerImpl Deploy Standard ComptrollerG7 15 | 16 | -- Baseline USDC comp speed before the patch 17 | Assert Equal (Comptroller CompSpeed cUSDC) 5.3786477206671197e16 18 | 19 | -- Mint tokens 20 | From DAIWhale (Trx GasPrice 0 (Erc20 DAI Approve cDAI UInt256Max)) 21 | From DAIWhale (Trx GasPrice 0 (CToken cDAI Mint 10000e6)) 22 | From USDCWhale (Trx GasPrice 0 (Erc20 USDC Approve cUSDC UInt256Max)) 23 | From USDCWhale (Trx GasPrice 0 (CToken cUSDC Mint 10000e6)) 24 | 25 | -- Baseline COMP claim speed 26 | Assert Equal (Comptroller CheckIsComped cDAI) True 27 | 28 | Comptroller ClaimComp BigBorrower 29 | AdvanceBlocks 1000 30 | Expect Changes (Erc20 Comp TokenBalance BigBorrower) 2.893496802261224189e18 31 | Comptroller ClaimComp BigBorrower 32 | 33 | -- Propose to apply the patch 34 | 35 | From CompHolder (Comp Delegate CompHolder) 36 | From CompHolder (Governor GovernorAlpha Propose "Disable automatic comp speed refresh and automatic claims and change comp speed" [(Address Unitroller) (Address ComptrollerG7) (Address Unitroller)] [0 0 0] ["_setPendingImplementation(address)" "_become(address)" "_setCompSpeed(address,uint256)"] [[(Address ComptrollerG7)] [(Address Unitroller)] [(Address cUSDC) 30000000000000000]]) 37 | 38 | -- Vote for, queue, and execute the proposal 39 | 40 | MineBlock 41 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 42 | AdvanceBlocks 20000 43 | Governor GovernorAlpha Proposal LastProposal Queue 44 | IncreaseTime 604910 45 | Governor GovernorAlpha Proposal LastProposal Execute 46 | ComptrollerImpl ComptrollerG7 MergeABI 47 | 48 | Comptroller ClaimComp BigBorrower 49 | AdvanceBlocks 1000 50 | Expect Changes (Erc20 Comp TokenBalance BigBorrower) 2.893496802261224189e18 51 | Comptroller ClaimComp BigBorrower 52 | 53 | Print "Upgrade OK!" 54 | -------------------------------------------------------------------------------- /spec/sim/0007-cdai-impl-migration/hypothetical_migration.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompHolder "0xC89b6f0146642688bb254bF93C28fcCF1E182C81" 5 | Alias DaiHolder "0xf977814e90da44bfa03b6295a0616a897441acec" 6 | Alias CUSDCHolder "0x5e34bc93a7506ecc8562ade4d5c8b090247a6349" 7 | Alias CUsdtImplementation "0x976aa93ca5aaa569109f4267589c619a097f001d" 8 | Alias CUsdtIRM "0xFB564da37B41b2F6B6EDcc3e56FbF523bD9F2012" 9 | Web3Fork "https://mainnet-eth.compound.finance/@11447815" (CompHolder DaiHolder CUSDCHolder) 10 | UseConfigs mainnet 11 | 12 | 13 | -- Delegate and propose update 14 | From CompHolder (Comp Delegate CompHolder) 15 | From CompHolder (Governor GovernorAlpha Propose "Update cDai implementation" [(Address cDai) (Address cDai)] [0 0] ["_setImplementation(address,bool,bytes)" "_setInterestRateModel(address)"] [[(address CUsdtImplementation) true "0x"] [(address CUsdtIRM)]]) 16 | 17 | -- Fast forward, vote, queue, execute 18 | MineBlock 19 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 20 | AdvanceBlocks 20000 21 | Governor GovernorAlpha Proposal LastProposal Queue 22 | IncreaseTime 604910 23 | Governor GovernorAlpha Proposal LastProposal Execute 24 | 25 | -- Assert Dai retrieved from DSR 26 | Assert Equal (Erc20 Dai TokenBalance cDai) (284058478303890709049107567) 27 | 28 | -- IRM changed correctly 29 | Assert Equal (CToken cDai InterestRateModel) (Address CUsdtIRM) 30 | 31 | -- Ensure accrue interest works 32 | CToken cDai AccrueInterest 33 | 34 | -- Mint Test 35 | From DaiHolder (Erc20 Dai Approve (Address cDai) 10000000) 36 | From DaiHolder (CToken cDai Mint 10000000) 37 | Assert Equal (Erc20 Dai TokenBalance cDai) (284058478303890709059107567) 38 | 39 | -- Borrow Test 40 | From CUSDCHolder (CToken cDai Borrow 10000000) 41 | Assert Equal (Erc20 Dai TokenBalance CUSDCHolder) (4235678125976069) 42 | Assert Equal (Erc20 Dai TokenBalance cDai) (284058478303890709049107567) 43 | 44 | Print "cDai implementation migration ok" -------------------------------------------------------------------------------- /spec/sim/0007-cdai-impl-migration/hypothetical_migration_post_propose.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompHolder "0xed409c9ff60f3020abf9012bcd45fc294f5608ff" 5 | Alias CompHolder2 "0x6626593c237f530d15ae9980a95ef938ac15c35c" 6 | Alias DaiHolder "0xf977814e90da44bfa03b6295a0616a897441acec" 7 | Alias CUSDCHolder "0x5e34bc93a7506ecc8562ade4d5c8b090247a6349" 8 | Alias CUsdtImplementation "0x976aa93ca5aaa569109f4267589c619a097f001d" 9 | Alias CUsdtIRM "0xFB564da37B41b2F6B6EDcc3e56FbF523bD9F2012" 10 | Web3Fork "https://mainnet-eth.compound.finance/@11499832" (CompHolder CompHolder2 DaiHolder CUSDCHolder) 11 | UseConfigs mainnet 12 | 13 | 14 | 15 | 16 | -- Fast forward, vote, queue, execute 17 | MineBlock 18 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 19 | From CompHolder2 (Governor GovernorAlpha Proposal LastProposal Vote For) 20 | AdvanceBlocks 20000 21 | Governor GovernorAlpha Proposal LastProposal Queue 22 | IncreaseTime 604910 23 | Governor GovernorAlpha Proposal LastProposal Execute 24 | 25 | -- Assert Dai retrieved from DSR 26 | Assert Equal (Erc20 Dai TokenBalance cDai) (247764889541500427246579984) 27 | 28 | -- Assert Impl Changed Correctly 29 | Assert Equal (CToken cDai Implementation) (Address CUsdtImplementation) 30 | 31 | -- IRM changed correctly 32 | Assert Equal (CToken cDai InterestRateModel) (Address CUsdtIRM) 33 | 34 | -- Ensure accrue interest works 35 | CToken cDai AccrueInterest 36 | 37 | -- Mint Test 38 | From DaiHolder (Erc20 Dai Approve (Address cDai) 10000000) 39 | From DaiHolder (CToken cDai Mint 10000000) 40 | Assert Equal (Erc20 Dai TokenBalance cDai) (247764889541500427256579984) 41 | 42 | -- Borrow Test 43 | From CUSDCHolder (CToken cDai Borrow 10000000) 44 | Assert Equal (Erc20 Dai TokenBalance CUSDCHolder) (4235678125976069) 45 | Assert Equal (Erc20 Dai TokenBalance cDai) (247764889541500427246579984) 46 | 47 | Print "cDai implementation migration ok" -------------------------------------------------------------------------------- /spec/sim/0008-sweep-token/hypothetical_migration.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompHolder "0xC89b6f0146642688bb254bF93C28fcCF1E182C81" 5 | Alias cUNISender "0xf22c2e3475e4a066f4e9f44567c950dd36112d05" 6 | Alias UniHolder "0x5518C31f4A22782E5a7046711FACF985985530c7" 7 | Alias CUSDCHolder "0x926e78b8DF67e129011750Dd7b975f8E50D3d7Ad" 8 | Web3Fork "https://mainnet-eth.compound.finance/@11746553" (CompHolder CUSDCHolder UniHolder) 9 | UseConfigs mainnet 10 | 11 | 12 | -- Delegate and propose update 13 | CTokenDelegate Deploy CErc20Delegate newCUNIImpl 14 | 15 | From CompHolder (Comp Delegate CompHolder) 16 | From CompHolder (Governor GovernorAlpha Propose "Update cUNI implementation" [(Address cUNI) (Address cUNI) (Address cUNI)] [0 0 0] ["_setImplementation(address,bool,bytes)" "sweepToken(address)" "transfer(address,uint256)"] [[(address newCUNIImpl) false "0x"] [(address cUNI)] [(address cUNISender) 1]]) 17 | 18 | -- Fast forward, vote, queue, execute 19 | MineBlock 20 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 21 | AdvanceBlocks 20000 22 | Governor GovernorAlpha Proposal LastProposal Queue 23 | IncreaseTime 604910 24 | Governor GovernorAlpha Proposal LastProposal Execute 25 | 26 | Assert Equal (Erc20 (Address cUNI) TokenBalance cUNISender) (1) 27 | Assert Equal (Erc20 (Address cUNI) TokenBalance Timelock) (179874258721489) 28 | 29 | -- Ensure accrue interest works 30 | CToken cUNI AccrueInterest 31 | 32 | -- Mint Test 33 | Assert Equal (Erc20 Uni TokenBalance cUNI) (8841949597201167092831625) 34 | From UniHolder (Erc20 Uni Approve cUNI 10000000) 35 | From UniHolder (CToken cUNI Mint 10000000) 36 | Assert Equal (Erc20 Uni TokenBalance cUNI) (8841949597201167102831625) 37 | 38 | -- Borrow Test 39 | From CUSDCHolder (CToken cUNI Borrow 10000000) 40 | Assert Equal (Erc20 Uni TokenBalance CUSDCHolder) (10000000) 41 | 42 | Print "cUNI implementation migration ok" 43 | -------------------------------------------------------------------------------- /spec/sim/0008-sweep-token/hypothetical_migration_post_deploy.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompHolder "0xC89b6f0146642688bb254bF93C28fcCF1E182C81" 5 | Alias cUNISender "0xf22c2e3475e4a066f4e9f44567c950dd36112d05" 6 | Alias UniHolder "0x5518C31f4A22782E5a7046711FACF985985530c7" 7 | Alias CUSDCHolder "0x926e78b8DF67e129011750Dd7b975f8E50D3d7Ad" 8 | Alias NewcUNIImplementation "0xa1849880593E96d2f7dF77D0D38a7f2372aE10E0" 9 | Web3Fork "https://mainnet-eth.compound.finance/@11805759" (CompHolder CUSDCHolder UniHolder) 10 | UseConfigs mainnet 11 | 12 | 13 | 14 | From CompHolder (Comp Delegate CompHolder) 15 | From CompHolder (Governor GovernorAlpha Propose "Update cUNI implementation" [(Address cUNI) (Address cUNI) (Address cUNI)] [0 0 0] ["_setImplementation(address,bool,bytes)" "sweepToken(address)" "transfer(address,uint256)"] [[(address NewcUNIImplementation) false "0x"] [(address cUNI)] [(address cUNISender) 1]]) 16 | 17 | -- Fast forward, vote, queue, execute 18 | MineBlock 19 | From CompHolder (Governor GovernorAlpha Proposal LastProposal Vote For) 20 | AdvanceBlocks 20000 21 | Governor GovernorAlpha Proposal LastProposal Queue 22 | IncreaseTime 604910 23 | Governor GovernorAlpha Proposal LastProposal Execute 24 | 25 | Assert Equal (Erc20 (Address cUNI) TokenBalance cUNISender) (1) 26 | Assert Equal (Erc20 (Address cUNI) TokenBalance Timelock) (179874258721489) 27 | 28 | -- Ensure accrue interest works 29 | MineBlock 30 | CToken cUNI AccrueInterest 31 | 32 | -- Mint Test 33 | Assert Equal (Erc20 Uni TokenBalance cUNI) (9213234645306032140780326) 34 | From UniHolder (Erc20 Uni Approve cUNI 10000000) 35 | From UniHolder (CToken cUNI Mint 10000000) 36 | Assert Equal (Erc20 Uni TokenBalance cUNI) (9213234645306032150780326) 37 | 38 | -- Borrow Test 39 | From CUSDCHolder (CToken cUNI Borrow 10000000) 40 | Assert Equal (Erc20 Uni TokenBalance CUSDCHolder) (10000000) 41 | 42 | Print "cUNI implementation migration ok" 43 | -------------------------------------------------------------------------------- /spec/sim/0008-sweep-token/hypothetical_migration_post_propose.scen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env yarn repl -s 2 | 3 | PrintTransactionLogs 4 | Alias CompVoter1 "0x6626593C237f530D15aE9980A95ef938Ac15c35c" 5 | Alias CompVoter2 "0xA1b61405791170833070C0eA61eD28728A840241" 6 | Alias CompVoter3 "0xAC5720d6EE2d7872b88914C9c5Fa9BF38e72FaF6" 7 | Alias CompVoter4 "0x8169522c2C57883E8EF80C498aAB7820dA539806" 8 | Alias Arr00 "0x2B384212EDc04Ae8bB41738D05BA20E33277bf33" 9 | Alias NewcUNIImplementation "0xa1849880593E96d2f7dF77D0D38a7f2372aE10E0" 10 | Alias UniHolder "0x5518C31f4A22782E5a7046711FACF985985530c7" 11 | Alias CUSDCHolder "0x926e78b8DF67e129011750Dd7b975f8E50D3d7Ad" 12 | Alias cUNISender "0xf22c2e3475e4a066f4e9f44567c950dd36112d05" 13 | Web3Fork "https://mainnet-eth.compound.finance/@11820163" (CompVoter1 CompVoter2 CompVoter3 CompVoter4 UniHolder CUSDCHolder) 14 | UseConfigs mainnet 15 | 16 | 17 | -- Vote for, queue, and execute the proposal 18 | MineBlock 19 | From CompVoter1 (Governor GovernorAlpha Proposal LastProposal Vote For) 20 | From CompVoter2 (Governor GovernorAlpha Proposal LastProposal Vote For) 21 | From CompVoter3 (Governor GovernorAlpha Proposal LastProposal Vote For) 22 | From CompVoter4 (Governor GovernorAlpha Proposal LastProposal Vote For) 23 | AdvanceBlocks 20000 24 | Governor GovernorAlpha Proposal LastProposal Queue 25 | IncreaseTime 604910 26 | Governor GovernorAlpha Proposal LastProposal Execute 27 | 28 | -- Check expected values 29 | Assert Equal (CToken cUNI Implementation) (Address NewcUNIImplementation) 30 | Assert Equal (Erc20 (Address cUNI) TokenBalance (Address Timelock)) (17987425872149) 31 | Assert Equal (Erc20 (Address cUNI) TokenBalance cUNISender) (161886832849341) 32 | Assert Equal (Erc20 (Address COMP) TokenBalance (Address Arr00)) (30040000000000000000) 33 | 34 | -- Ensure accrue interest works 35 | MineBlock 36 | CToken cUNI AccrueInterest 37 | 38 | -- Mint Test 39 | Assert Equal (Erc20 Uni TokenBalance cUNI) (8974517540659969962306996) 40 | From UniHolder (Erc20 Uni Approve cUNI 10000000) 41 | From UniHolder (CToken cUNI Mint 10000000) 42 | Assert Equal (Erc20 Uni TokenBalance cUNI) (8974517540659969972306996) 43 | 44 | -- Borrow Test 45 | From CUSDCHolder (CToken cUNI Borrow 10000000) 46 | Assert Equal (Erc20 Uni TokenBalance CUSDCHolder) (10000000) 47 | 48 | Print "cUNI implementation migration ok" -------------------------------------------------------------------------------- /tests/CompilerTest.js: -------------------------------------------------------------------------------- 1 | const { 2 | etherBalance, 3 | etherGasCost, 4 | getContract 5 | } = require('./Utils/Ethereum'); 6 | 7 | const { 8 | makeComptroller, 9 | makeCToken, 10 | makePriceOracle, 11 | pretendBorrow, 12 | borrowSnapshot 13 | } = require('./Utils/Compound'); 14 | 15 | describe('Const', () => { 16 | it("does the right thing and not too expensive", async () => { 17 | const base = await deploy('ConstBase'); 18 | const sub = await deploy('ConstSub'); 19 | expect(await call(base, 'c')).toEqual("1"); 20 | expect(await call(sub, 'c')).toEqual("2"); 21 | expect(await call(base, 'ADD', [1])).toEqual("2"); 22 | expect(await call(base, 'add', [1])).toEqual("2"); 23 | expect(await call(sub, 'ADD', [1])).toEqual("2"); 24 | expect(await call(sub, 'add', [1])).toEqual("3"); 25 | 26 | const tx1 = await send(base, 'ADD', [1]); 27 | const tx2 = await send(base, 'add', [1]); 28 | const tx3 = await send(sub, 'ADD', [1]); 29 | const tx4 = await send(sub, 'add', [1]); 30 | expect(Math.abs(tx2.gasUsed - tx1.gasUsed) < 20); 31 | expect(Math.abs(tx4.gasUsed - tx3.gasUsed) < 20); 32 | }); 33 | }); 34 | 35 | describe('Structs', () => { 36 | it("only writes one slot", async () => { 37 | const structs1 = await deploy('Structs'); 38 | const tx1_0 = await send(structs1, 'writeEach', [0, 1, 2, 3]); 39 | const tx1_1 = await send(structs1, 'writeEach', [0, 1, 2, 3]); 40 | const tx1_2 = await send(structs1, 'writeOnce', [0, 1, 2, 3]); 41 | 42 | const structs2 = await deploy('Structs'); 43 | const tx2_0 = await send(structs2, 'writeOnce', [0, 1, 2, 3]); 44 | const tx2_1 = await send(structs2, 'writeOnce', [0, 1, 2, 3]); 45 | const tx2_2 = await send(structs2, 'writeEach', [0, 1, 2, 3]); 46 | 47 | expect(tx1_0.gasUsed < tx2_0.gasUsed); // each beats once 48 | expect(tx1_1.gasUsed < tx2_1.gasUsed); // each beats once 49 | expect(tx1_2.gasUsed > tx2_2.gasUsed); // each beats once 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/Contracts/CompHarness.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../contracts/Governance/TROP.sol"; 5 | 6 | contract CompScenario is TROP { 7 | constructor(address account) TROP(account) public {} 8 | 9 | function transferScenario(address[] calldata destinations, uint256 amount) external returns (bool) { 10 | for (uint i = 0; i < destinations.length; i++) { 11 | address dst = destinations[i]; 12 | _transferTokens(msg.sender, dst, uint96(amount)); 13 | } 14 | return true; 15 | } 16 | 17 | function transferFromScenario(address[] calldata froms, uint256 amount) external returns (bool) { 18 | for (uint i = 0; i < froms.length; i++) { 19 | address from = froms[i]; 20 | _transferTokens(from, msg.sender, uint96(amount)); 21 | } 22 | return true; 23 | } 24 | 25 | function generateCheckpoints(uint count, uint offset) external { 26 | for (uint i = 1 + offset; i <= count + offset; i++) { 27 | checkpoints[msg.sender][numCheckpoints[msg.sender]++] = Checkpoint(uint32(i), uint96(i)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Contracts/ComptrollerScenarioG1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/ComptrollerG1.sol"; 4 | import "../../contracts/PriceOracle.sol"; 5 | 6 | // XXX we should delete G1 everything... 7 | // requires fork/deploy bytecode tests 8 | 9 | contract ComptrollerScenarioG1 is ComptrollerG1 { 10 | uint public blockNumber; 11 | 12 | constructor() ComptrollerG1() public {} 13 | 14 | function membershipLength(CToken cToken) public view returns (uint) { 15 | return accountAssets[address(cToken)].length; 16 | } 17 | 18 | function fastForward(uint blocks) public returns (uint) { 19 | blockNumber += blocks; 20 | 21 | return blockNumber; 22 | } 23 | 24 | function setBlockNumber(uint number) public { 25 | blockNumber = number; 26 | } 27 | 28 | function _become( 29 | Unitroller unitroller, 30 | PriceOracle _oracle, 31 | uint _closeFactorMantissa, 32 | uint _maxAssets, 33 | bool reinitializing) public { 34 | super._become(unitroller, _oracle, _closeFactorMantissa, _maxAssets, reinitializing); 35 | } 36 | 37 | function getHypotheticalAccountLiquidity( 38 | address account, 39 | address cTokenModify, 40 | uint redeemTokens, 41 | uint borrowAmount) public view returns (uint, uint, uint) { 42 | (Error err, uint liquidity, uint shortfall) = super.getHypotheticalAccountLiquidityInternal( 43 | account, CToken(cTokenModify), redeemTokens, borrowAmount); 44 | return (uint(err), liquidity, shortfall); 45 | } 46 | 47 | function unlist(CToken cToken) public { 48 | markets[address(cToken)].isListed = false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Contracts/ComptrollerScenarioG2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/ComptrollerG2.sol"; 4 | 5 | contract ComptrollerScenarioG2 is ComptrollerG2 { 6 | uint public blockNumber; 7 | address public compAddress; 8 | 9 | constructor() ComptrollerG2() public {} 10 | 11 | function fastForward(uint blocks) public returns (uint) { 12 | blockNumber += blocks; 13 | return blockNumber; 14 | } 15 | 16 | function setBlockNumber(uint number) public { 17 | blockNumber = number; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Contracts/ComptrollerScenarioG3.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/ComptrollerG3.sol"; 4 | 5 | contract ComptrollerScenarioG3 is ComptrollerG3 { 6 | uint public blockNumber; 7 | address public compAddress; 8 | 9 | constructor() ComptrollerG3() public {} 10 | 11 | function setCompAddress(address compAddress_) public { 12 | compAddress = compAddress_; 13 | } 14 | 15 | function getCompAddress() public view returns (address) { 16 | return compAddress; 17 | } 18 | 19 | function membershipLength(CToken cToken) public view returns (uint) { 20 | return accountAssets[address(cToken)].length; 21 | } 22 | 23 | function fastForward(uint blocks) public returns (uint) { 24 | blockNumber += blocks; 25 | 26 | return blockNumber; 27 | } 28 | 29 | function setBlockNumber(uint number) public { 30 | blockNumber = number; 31 | } 32 | 33 | function getBlockNumber() public view returns (uint) { 34 | return blockNumber; 35 | } 36 | 37 | function getCompMarkets() public view returns (address[] memory) { 38 | uint m = allMarkets.length; 39 | uint n = 0; 40 | for (uint i = 0; i < m; i++) { 41 | if (markets[address(allMarkets[i])].isComped) { 42 | n++; 43 | } 44 | } 45 | 46 | address[] memory compMarkets = new address[](n); 47 | uint k = 0; 48 | for (uint i = 0; i < m; i++) { 49 | if (markets[address(allMarkets[i])].isComped) { 50 | compMarkets[k++] = address(allMarkets[i]); 51 | } 52 | } 53 | return compMarkets; 54 | } 55 | 56 | function unlist(CToken cToken) public { 57 | markets[address(cToken)].isListed = false; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Contracts/ComptrollerScenarioG4.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/ComptrollerG4.sol"; 4 | 5 | contract ComptrollerScenarioG4 is ComptrollerG4 { 6 | uint public blockNumber; 7 | address public compAddress; 8 | 9 | constructor() ComptrollerG4() public {} 10 | 11 | function fastForward(uint blocks) public returns (uint) { 12 | blockNumber += blocks; 13 | return blockNumber; 14 | } 15 | 16 | function setBlockNumber(uint number) public { 17 | blockNumber = number; 18 | } 19 | 20 | function membershipLength(CToken cToken) public view returns (uint) { 21 | return accountAssets[address(cToken)].length; 22 | } 23 | 24 | function unlist(CToken cToken) public { 25 | markets[address(cToken)].isListed = false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Contracts/ComptrollerScenarioG5.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/ComptrollerG5.sol"; 4 | 5 | contract ComptrollerScenarioG5 is ComptrollerG5 { 6 | uint public blockNumber; 7 | address public compAddress; 8 | 9 | constructor() ComptrollerG5() public {} 10 | 11 | function setCompAddress(address compAddress_) public { 12 | compAddress = compAddress_; 13 | } 14 | 15 | function getCompAddress() public view returns (address) { 16 | return compAddress; 17 | } 18 | 19 | function membershipLength(CToken cToken) public view returns (uint) { 20 | return accountAssets[address(cToken)].length; 21 | } 22 | 23 | function fastForward(uint blocks) public returns (uint) { 24 | blockNumber += blocks; 25 | 26 | return blockNumber; 27 | } 28 | 29 | function setBlockNumber(uint number) public { 30 | blockNumber = number; 31 | } 32 | 33 | function getBlockNumber() public view returns (uint) { 34 | return blockNumber; 35 | } 36 | 37 | function getCompMarkets() public view returns (address[] memory) { 38 | uint m = allMarkets.length; 39 | uint n = 0; 40 | for (uint i = 0; i < m; i++) { 41 | if (markets[address(allMarkets[i])].isComped) { 42 | n++; 43 | } 44 | } 45 | 46 | address[] memory compMarkets = new address[](n); 47 | uint k = 0; 48 | for (uint i = 0; i < m; i++) { 49 | if (markets[address(allMarkets[i])].isComped) { 50 | compMarkets[k++] = address(allMarkets[i]); 51 | } 52 | } 53 | return compMarkets; 54 | } 55 | 56 | function unlist(CToken cToken) public { 57 | markets[address(cToken)].isListed = false; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Contracts/ComptrollerScenarioG6.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/ComptrollerG6.sol"; 4 | 5 | contract ComptrollerScenarioG6 is ComptrollerG6 { 6 | uint public blockNumber; 7 | address public compAddress; 8 | 9 | constructor() ComptrollerG6() public {} 10 | 11 | function fastForward(uint blocks) public returns (uint) { 12 | blockNumber += blocks; 13 | return blockNumber; 14 | } 15 | 16 | function setCompAddress(address compAddress_) public { 17 | compAddress = compAddress_; 18 | } 19 | 20 | function getCompAddress() public view returns (address) { 21 | return compAddress; 22 | } 23 | 24 | function setBlockNumber(uint number) public { 25 | blockNumber = number; 26 | } 27 | 28 | function getBlockNumber() public view returns (uint) { 29 | return blockNumber; 30 | } 31 | 32 | function membershipLength(CToken cToken) public view returns (uint) { 33 | return accountAssets[address(cToken)].length; 34 | } 35 | 36 | function unlist(CToken cToken) public { 37 | markets[address(cToken)].isListed = false; 38 | } 39 | 40 | function setCompSpeed(address cToken, uint compSpeed) public { 41 | compSpeeds[cToken] = compSpeed; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Contracts/Const.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | contract ConstBase { 4 | uint public constant C = 1; 5 | 6 | function c() public pure returns (uint) { 7 | return 1; 8 | } 9 | 10 | function ADD(uint a) public view returns (uint) { 11 | // tells compiler to accept view instead of pure 12 | if (false) { 13 | C + now; 14 | } 15 | return a + C; 16 | } 17 | 18 | function add(uint a) public view returns (uint) { 19 | // tells compiler to accept view instead of pure 20 | if (false) { 21 | C + now; 22 | } 23 | return a + c(); 24 | } 25 | } 26 | 27 | contract ConstSub is ConstBase { 28 | function c() public pure returns (uint) { 29 | return 2; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Contracts/Counter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | contract Counter { 4 | uint public count; 5 | uint public count2; 6 | 7 | function increment(uint amount) public payable { 8 | count += amount; 9 | } 10 | 11 | function decrement(uint amount) public payable { 12 | require(amount <= count, "counter underflow"); 13 | count -= amount; 14 | } 15 | 16 | function increment(uint amount, uint amount2) public payable { 17 | count += amount; 18 | count2 += amount2; 19 | } 20 | 21 | function notZero() public view { 22 | require(count != 0, "Counter::notZero"); 23 | } 24 | 25 | function doRevert() public pure { 26 | require(false, "Counter::revert Testing"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Contracts/EvilToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./FaucetToken.sol"; 4 | 5 | /** 6 | * @title The Compound Evil Test Token 7 | * @author tropykus 8 | * @notice A simple test token that fails certain operations 9 | */ 10 | contract EvilToken is FaucetToken { 11 | bool public fail; 12 | 13 | constructor(uint256 _initialAmount, string memory _tokenName, uint8 _decimalUnits, string memory _tokenSymbol) public 14 | FaucetToken(_initialAmount, _tokenName, _decimalUnits, _tokenSymbol) { 15 | fail = true; 16 | } 17 | 18 | function setFail(bool _fail) external { 19 | fail = _fail; 20 | } 21 | 22 | function transfer(address dst, uint256 amount) external returns (bool) { 23 | if (fail) { 24 | return false; 25 | } 26 | balanceOf[msg.sender] = balanceOf[msg.sender].sub(amount); 27 | balanceOf[dst] = balanceOf[dst].add(amount); 28 | emit Transfer(msg.sender, dst, amount); 29 | return true; 30 | } 31 | 32 | function transferFrom(address src, address dst, uint256 amount) external returns (bool) { 33 | if (fail) { 34 | return false; 35 | } 36 | balanceOf[src] = balanceOf[src].sub(amount); 37 | balanceOf[dst] = balanceOf[dst].add(amount); 38 | allowance[src][msg.sender] = allowance[src][msg.sender].sub(amount); 39 | emit Transfer(src, dst, amount); 40 | return true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Contracts/FalseMarker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | contract FalseMarkerMethodComptroller { 4 | bool public constant isComptroller = false; 5 | } 6 | 7 | contract FalseMarkerMethodInterestRateModel { 8 | bool public constant isInterestRateModel = false; 9 | } 10 | -------------------------------------------------------------------------------- /tests/Contracts/Fauceteer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/EIP20NonStandardInterface.sol"; 4 | 5 | /** 6 | * @title Fauceteer 7 | * @author tropykus 8 | * @notice First computer program to be part of The Giving Pledge 9 | */ 10 | contract Fauceteer { 11 | 12 | /** 13 | * @notice Drips some tokens to caller 14 | * @dev We send 0.01% of our tokens to the caller. Over time, the amount will tend toward and eventually reach zero. 15 | * @param token The token to drip. Note: if we have no balance in this token, function will revert. 16 | */ 17 | function drip(EIP20NonStandardInterface token) public { 18 | uint myBalance = token.balanceOf(address(this)); 19 | require(myBalance > 0, "Fauceteer is empty"); 20 | token.transfer(msg.sender, myBalance / 10000); // 0.01% 21 | 22 | bool success; 23 | assembly { 24 | switch returndatasize() 25 | case 0 { // This is a non-standard ERC-20 26 | success := not(0) // set success to true 27 | } 28 | case 32 { // This is a compliant ERC-20 29 | returndatacopy(0, 0, 32) 30 | success := mload(0) // Set `success = returndata` of external call 31 | } 32 | default { // This is an excessively non-compliant ERC-20, revert. 33 | revert(0, 0) 34 | } 35 | } 36 | 37 | require(success, "Transfer returned false."); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Contracts/FeeToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "./FaucetToken.sol"; 4 | 5 | /** 6 | * @title Fee Token 7 | * @author tropykus 8 | * @notice A simple test token that charges fees on transfer. Used to mock USDT. 9 | */ 10 | contract FeeToken is FaucetToken { 11 | uint public basisPointFee; 12 | address public owner; 13 | 14 | constructor( 15 | uint256 _initialAmount, 16 | string memory _tokenName, 17 | uint8 _decimalUnits, 18 | string memory _tokenSymbol, 19 | uint _basisPointFee, 20 | address _owner 21 | ) FaucetToken(_initialAmount, _tokenName, _decimalUnits, _tokenSymbol) public { 22 | basisPointFee = _basisPointFee; 23 | owner = _owner; 24 | } 25 | 26 | function transfer(address dst, uint amount) public returns (bool) { 27 | uint fee = amount.mul(basisPointFee).div(10000); 28 | uint net = amount.sub(fee); 29 | balanceOf[owner] = balanceOf[owner].add(fee); 30 | balanceOf[msg.sender] = balanceOf[msg.sender].sub(amount); 31 | balanceOf[dst] = balanceOf[dst].add(net); 32 | emit Transfer(msg.sender, dst, amount); 33 | return true; 34 | } 35 | 36 | function transferFrom(address src, address dst, uint amount) public returns (bool) { 37 | uint fee = amount.mul(basisPointFee).div(10000); 38 | uint net = amount.sub(fee); 39 | balanceOf[owner] = balanceOf[owner].add(fee); 40 | balanceOf[src] = balanceOf[src].sub(amount); 41 | balanceOf[dst] = balanceOf[dst].add(net); 42 | allowance[src][msg.sender] = allowance[src][msg.sender].sub(amount); 43 | emit Transfer(src, dst, amount); 44 | return true; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Contracts/FixedPriceOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/PriceOracle.sol"; 4 | 5 | contract FixedPriceOracle is PriceOracle { 6 | uint public price; 7 | 8 | constructor(uint _price) public { 9 | price = _price; 10 | } 11 | 12 | function getUnderlyingPrice(CToken cToken) public view returns (uint) { 13 | cToken; 14 | return price; 15 | } 16 | 17 | function assetPrices(address asset) public view returns (uint) { 18 | asset; 19 | return price; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Contracts/GovernorAlphaHarness.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../contracts/Governance/GovernorAlpha.sol"; 5 | 6 | contract GovernorAlphaHarness is GovernorAlpha { 7 | constructor(address timelock_, address comp_, address guardian_) GovernorAlpha(timelock_, comp_, guardian_) public {} 8 | 9 | function votingPeriod() public pure returns (uint) { return 240; } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Contracts/InterestRateModelHarness.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/InterestRateModel.sol"; 4 | 5 | /** 6 | * @title An Interest Rate Model for tests that can be instructed to return a failure instead of doing a calculation 7 | * @author tropykus 8 | */ 9 | contract InterestRateModelHarness is InterestRateModel { 10 | uint public constant opaqueBorrowFailureCode = 20; 11 | bool public failBorrowRate; 12 | uint public borrowRate; 13 | 14 | constructor(uint borrowRate_) public { 15 | borrowRate = borrowRate_; 16 | blocksPerYear = 1051200; 17 | } 18 | 19 | function setFailBorrowRate(bool failBorrowRate_) public { 20 | failBorrowRate = failBorrowRate_; 21 | } 22 | 23 | function setBorrowRate(uint borrowRate_) public { 24 | borrowRate = borrowRate_; 25 | } 26 | 27 | function getBorrowRate(uint _cash, uint _borrows, uint _reserves) public view returns (uint) { 28 | _cash; // unused 29 | _borrows; // unused 30 | _reserves; // unused 31 | require(!failBorrowRate, "INTEREST_RATE_MODEL_ERROR"); 32 | return borrowRate; 33 | } 34 | 35 | function getSupplyRate(uint _cash, uint _borrows, uint _reserves, uint _reserveFactor) external view returns (uint) { 36 | _cash; // unused 37 | _borrows; // unused 38 | _reserves; // unused 39 | return borrowRate * (1 - _reserveFactor); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Contracts/MathHelpers.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | contract MathHelpers { 4 | 5 | /* 6 | * @dev Creates a number like 15e16 as a uint256 from scientific(15, 16). 7 | */ 8 | function scientific(uint val, uint expTen) internal pure returns (uint) { 9 | return val * ( 10 ** expTen ); 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /tests/Contracts/MockMCD.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | 4 | contract MockPot { 5 | 6 | uint public dsr; // the Dai Savings Rate 7 | 8 | constructor(uint dsr_) public { 9 | setDsr(dsr_); 10 | } 11 | 12 | function setDsr(uint dsr_) public { 13 | dsr = dsr_; 14 | } 15 | } 16 | 17 | contract MockJug { 18 | 19 | struct Ilk { 20 | uint duty; 21 | uint rho; 22 | } 23 | 24 | mapping (bytes32 => Ilk) public ilks; 25 | uint public base; 26 | 27 | constructor(uint duty_, uint base_) public { 28 | setETHDuty(duty_); 29 | setBase(base_); 30 | } 31 | 32 | function setBase(uint base_) public { 33 | base = base_; 34 | } 35 | 36 | function setETHDuty(uint duty_) public { 37 | ilks["ETH-A"].duty = duty_; 38 | } 39 | } -------------------------------------------------------------------------------- /tests/Contracts/PriceOracleProxyExtends.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/PriceOracleProxy.sol"; 4 | import "../../contracts/PriceOracleAdapter.sol"; 5 | 6 | contract PriceOracleProxyExtends is PriceOracleProxy { 7 | /// @notice Address of the adapter MoC to mock 8 | address public adapterMockAddress; 9 | 10 | /// @param guardian_ The address of the guardian, which may set the of PriceOracleProxy 11 | constructor(address guardian_) public PriceOracleProxy(guardian_) {} 12 | 13 | /** 14 | * @notice Get the underlying price of a listed cToken asset 15 | * @param cToken The cToken to get the underlying price of 16 | * @return The underlying asset price mantissa (scaled by 1e18) 17 | // */ 18 | function getUnderlyingPrice(CToken cToken) public view returns (uint256) { 19 | address oracleAdapter = tokenAdapter[address(cToken)]; 20 | //validate mapping 21 | if (oracleAdapter == address(0)) { 22 | //rewrite and return the asset prices of adapter MoC 23 | return 24 | PriceOracleAdapter(adapterMockAddress).assetPrices( 25 | address(cToken) 26 | ); 27 | } 28 | return PriceOracleAdapter(oracleAdapter).assetPrices(address(cToken)); 29 | } 30 | 31 | /** 32 | * @notice Set the address or adapter MoC 33 | * @return The underlying asset price mantissa (scaled by 1e18) 34 | // */ 35 | function setMockAdapter(address addressAdapter) public { 36 | //validate only guardian can set 37 | require( 38 | msg.sender == guardian, 39 | "PriceOracleDispatcher: only guardian may set the address" 40 | ); 41 | adapterMockAddress = addressAdapter; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Contracts/Structs.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | pragma experimental ABIEncoderV2; 3 | 4 | contract Structs { 5 | struct Outer { 6 | uint sentinel; 7 | mapping(address => Inner) inners; 8 | } 9 | 10 | struct Inner { 11 | uint16 a; 12 | uint16 b; 13 | uint96 c; 14 | } 15 | 16 | mapping(uint => Outer) public outers; 17 | 18 | function writeEach(uint id, uint16 a, uint16 b, uint96 c) public { 19 | Inner storage inner = outers[id].inners[msg.sender]; 20 | inner.a = a; 21 | inner.b = b; 22 | inner.c = c; 23 | } 24 | 25 | function writeOnce(uint id, uint16 a, uint16 b, uint96 c) public { 26 | Inner memory inner = Inner({a: a, b: b, c: c}); 27 | outers[id].inners[msg.sender] = inner; 28 | } 29 | } -------------------------------------------------------------------------------- /tests/Contracts/TetherInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/EIP20Interface.sol"; 4 | 5 | contract TetherInterface is EIP20Interface { 6 | function setParams(uint newBasisPoints, uint newMaxFee) external; 7 | } -------------------------------------------------------------------------------- /tests/Contracts/TimelockHarness.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.16; 2 | 3 | import "../../contracts/Timelock.sol"; 4 | 5 | interface Administered { 6 | function _acceptAdmin() external returns (uint); 7 | } 8 | 9 | contract TimelockHarness is Timelock { 10 | constructor(address admin_, uint delay_) 11 | Timelock(admin_, delay_) public { 12 | } 13 | 14 | function harnessSetPendingAdmin(address pendingAdmin_) public { 15 | pendingAdmin = pendingAdmin_; 16 | } 17 | 18 | function harnessSetAdmin(address admin_) public { 19 | admin = admin_; 20 | } 21 | } 22 | 23 | contract TimelockTest is Timelock { 24 | constructor(address admin_, uint delay_) Timelock(admin_, 2 days) public { 25 | delay = delay_; 26 | } 27 | 28 | function harnessSetAdmin(address admin_) public { 29 | require(msg.sender == admin,"Not admin!"); 30 | admin = admin_; 31 | } 32 | 33 | function harnessAcceptAdmin(Administered administered) public { 34 | administered._acceptAdmin(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | * This module loads Error and FailureInfo enum from ErrorReporter.sol. 5 | */ 6 | 7 | const path = require('path'); 8 | const solparse = require('solparse'); 9 | 10 | const errorReporterPath = path.join(__dirname, '..', 'contracts', 'ErrorReporter.sol'); 11 | const contents = solparse.parseFile(errorReporterPath); 12 | const [ 13 | ComptrollerErrorReporter, 14 | TokenErrorReporter 15 | ] = contents.body.filter(k => k.type === 'ContractStatement'); 16 | 17 | function invert(object) { 18 | return Object.entries(object).reduce((obj, [key, value]) => ({ ...obj, [value]: key }), {}); 19 | } 20 | 21 | function parse(reporter) { 22 | const ErrorInv = reporter.body.find(k => k.name == 'Error').members; 23 | const FailureInfoInv = reporter.body.find(k => k.name == 'FailureInfo').members; 24 | const Error = invert(ErrorInv); 25 | const FailureInfo = invert(FailureInfoInv); 26 | return {Error, FailureInfo, ErrorInv, FailureInfoInv}; 27 | } 28 | 29 | const carefulMathPath = path.join(__dirname, '..', 'contracts', 'CarefulMath.sol'); 30 | const CarefulMath = solparse.parseFile(carefulMathPath).body.find(k => k.type === 'ContractStatement'); 31 | const MathErrorInv = CarefulMath.body.find(k => k.name == 'MathError').members; 32 | const MathError = invert(MathErrorInv); 33 | 34 | const whitePaperModelPath = path.join(__dirname, '..', 'contracts', 'WhitePaperInterestRateModel.sol'); 35 | const whitePaperModel = solparse.parseFile(whitePaperModelPath).body.find(k => k.type === 'ContractStatement'); 36 | 37 | module.exports = { 38 | ComptrollerErr: parse(ComptrollerErrorReporter), 39 | TokenErr: parse(TokenErrorReporter), 40 | MathErr: { 41 | Error: MathError, 42 | ErrorInv: MathErrorInv 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /tests/Flywheel/GasTest.js: -------------------------------------------------------------------------------- 1 | const { 2 | makeComptroller, 3 | makeCToken 4 | } = require('../Utils/Compound'); 5 | const { 6 | etherExp, 7 | etherDouble, 8 | etherUnsigned 9 | } = require('../Utils/Ethereum'); 10 | 11 | 12 | // NB: coverage doesn't like this 13 | describe.skip('Flywheel trace ops', () => { 14 | let root, a1, a2, a3, accounts; 15 | let comptroller, market; 16 | beforeEach(async () => { 17 | let interestRateModelOpts = {borrowRate: 0.000001}; 18 | [root, a1, a2, a3, ...accounts] = saddle.accounts; 19 | comptroller = await makeComptroller(); 20 | market = await makeCToken({comptroller, supportMarket: true, underlyingPrice: 3, interestRateModelOpts}); 21 | await send(comptroller, '_addCompMarkets', [[market].map(c => c._address)]); 22 | }); 23 | 24 | it('update supply index SSTOREs', async () => { 25 | await send(comptroller, 'setBlockNumber', [100]); 26 | await send(market, 'harnessSetTotalBorrows', [etherUnsigned(11e18)]); 27 | await send(comptroller, 'setCompSpeed', [market._address, etherExp(0.5)]); 28 | 29 | const tx = await send(comptroller, 'harnessUpdateCompSupplyIndex', [market._address]); 30 | 31 | const ops = {}; 32 | await saddle.trace(tx, { 33 | execLog: log => { 34 | if (log.lastLog != undefined) { 35 | ops[log.op] = (ops[log.op] || []).concat(log); 36 | } 37 | } 38 | }); 39 | expect(ops.SSTORE.length).toEqual(1); 40 | }); 41 | 42 | it('update borrow index SSTOREs', async () => { 43 | await send(comptroller, 'setBlockNumber', [100]); 44 | await send(market, 'harnessSetTotalBorrows', [etherUnsigned(11e18)]); 45 | await send(comptroller, 'setCompSpeed', [market._address, etherExp(0.5)]); 46 | 47 | const tx = await send(comptroller, 'harnessUpdateCompBorrowIndex', [market._address, etherExp(1.1)]); 48 | 49 | const ops = {}; 50 | await saddle.trace(tx, { 51 | execLog: log => { 52 | if (log.lastLog != undefined) { 53 | ops[log.op] = (ops[log.op] || []).concat(log); 54 | } 55 | } 56 | }); 57 | expect(ops.SSTORE.length).toEqual(1); 58 | }); 59 | }); -------------------------------------------------------------------------------- /tests/Governance/CompScenarioTest.js: -------------------------------------------------------------------------------- 1 | 2 | describe('CompScenario', () => { 3 | let root, accounts; 4 | let comp; 5 | 6 | beforeEach(async () => { 7 | [root, ...accounts] = saddle.accounts; 8 | comp = await deploy('CompScenario', [root]); 9 | }); 10 | 11 | describe('lookup curve', () => { 12 | [ 13 | [1, 3], 14 | [2, 5], 15 | [20, 8], 16 | [100, 10], 17 | [500, 12], 18 | ...(process.env['SLOW'] ? [ [5000, 16], [20000, 18] ] : []) 19 | ].forEach(([checkpoints, expectedReads]) => { 20 | it(`with ${checkpoints} checkpoints, has ${expectedReads} reads`, async () => { 21 | let remaining = checkpoints; 22 | let offset = 0; 23 | while (remaining > 0) { 24 | let amt = remaining > 1000 ? 1000 : remaining; 25 | await comp.methods.generateCheckpoints(amt, offset).send({from: root, gas: 200000000}); 26 | remaining -= amt; 27 | offset += amt; 28 | } 29 | 30 | let result = await comp.methods.getPriorVotes(root, 1).send(); 31 | 32 | let networkId = await saddle.web3.eth.net.getId(); 33 | if (networkId < 30 && networkId > 33) { 34 | await saddle.trace(result, { 35 | constants: { 36 | "account": root 37 | }, 38 | preFilter: ({op}) => op === 'SLOAD', 39 | postFilter: ({source}) => !source || !source.includes('mockBlockNumber'), 40 | execLog: (log) => { 41 | if (process.env['VERBOSE']) { 42 | log.show(); 43 | } 44 | }, 45 | exec: (logs, info) => { 46 | expect(logs.length).toEqual(expectedReads); 47 | } 48 | }); 49 | } 50 | }, 600000); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /tests/Jest.js: -------------------------------------------------------------------------------- 1 | 2 | /* global jest */ 3 | jest.setTimeout(300000); -------------------------------------------------------------------------------- /tests/Scenarios/AddReservesScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('AddReserves.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/BorrowBalanceScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('BorrowBalance.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/BorrowCapScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('BorrowCap.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/BorrowEthScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('BorrowEth.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/BorrowScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('Borrow.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/BorrowWBTCScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('BorrowWBTC.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/BreakLiquidateScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('BreakLiquidate.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/CTokenAdminScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('CTokenAdmin.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/ChangeDelegateScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('ChangeDelegate.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/EnterExitMarketsScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('EnterExitMarkets.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/ExchangeRateScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('ExchangeRate.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/FeeScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('Fee.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Flywheel/CompSpeedScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Flywheel/CompSpeed.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Flywheel/FlywheelScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Flywheel/Flywheel.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Flywheel/GrantsScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Flywheel/Grants.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Flywheel/ReservoirScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Flywheel/Reservoir.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Governor/CancelScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Governor/Cancel.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Governor/DefeatScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Governor/Defeat.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Governor/ExecuteScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Governor/Execute.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Governor/GuardianScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Governor/Guardian.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Governor/ProposeScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Governor/Propose.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Governor/QueueScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Governor/Queue.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Governor/UpgradeScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Governor/Upgrade.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/Governor/VoteScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('Governor/Vote.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/HypotheticalAccountLiquidityScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('HypotheticalAccountLiquidity.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/InKindLiquidationScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('InKindLiquidation.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/MintEthScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('MintEth.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/MintScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('Mint.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/MintWBTCScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('MintWBTC.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/PriceOracleProxyScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('PriceOracleProxy.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RLEN/RLENScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../../Scenario'); 2 | 3 | scenario.run('RLEN/RLEN.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/ReEntryScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('ReEntry.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RedeemEthScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('RedeemEth.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RedeemScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('Redeem.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RedeemUnderlyingEthScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('RedeemUnderlyingEth.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RedeemUnderlyingScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('RedeemUnderlying.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RedeemUnderlyingWBTCScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('RedeemUnderlyingWBTC.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RedeemWBTCScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('RedeemWBTC.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/ReduceReservesScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('ReduceReserves.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RepayBorrowEthScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('RepayBorrowEth.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RepayBorrowScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('RepayBorrow.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/RepayBorrowWBTCScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('RepayBorrowWBTC.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/SeizeScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('Seize.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/SetComptrollerScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('SetComptroller.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/SweepTokenScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('SweepToken.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/TetherScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('Tether.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/TimelockScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('Timelock.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/TokenTransferScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('TokenTransfer.scen'); 4 | -------------------------------------------------------------------------------- /tests/Scenarios/UnitrollerScenTest.js: -------------------------------------------------------------------------------- 1 | const scenario = require('../Scenario'); 2 | 3 | scenario.run('Unitroller.scen'); 4 | -------------------------------------------------------------------------------- /tests/Tokens/compLikeTest.js: -------------------------------------------------------------------------------- 1 | const { 2 | makeCToken, 3 | } = require('../Utils/Compound'); 4 | 5 | 6 | describe('CCompLikeDelegate', function () { 7 | describe("_delegateCompLikeTo", () => { 8 | it("does not delegate if not the admin", async () => { 9 | const [root, a1] = saddle.accounts; 10 | const cToken = await makeCToken({kind: 'ccomp'}); 11 | await expect(send(cToken, '_delegateCompLikeTo', [a1], {from: a1})).rejects.toRevert('revert E7'); 12 | }); 13 | 14 | it("delegates successfully if the admin", async () => { 15 | const [root, a1] = saddle.accounts, amount = 1; 16 | const cCOMP = await makeCToken({kind: 'ccomp'}), COMP = cCOMP.underlying; 17 | const tx1 = await send(cCOMP, '_delegateCompLikeTo', [a1]); 18 | const tx2 = await send(COMP, 'transfer', [cCOMP._address, amount]); 19 | await expect(await call(COMP, 'getCurrentVotes', [a1])).toEqualNumber(amount); 20 | }); 21 | }); 22 | }); -------------------------------------------------------------------------------- /tests/Tokens/safeTokenTest.js: -------------------------------------------------------------------------------- 1 | const { 2 | makeCToken, 3 | getBalances, 4 | adjustBalances 5 | } = require('../Utils/Compound'); 6 | 7 | const exchangeRate = 5; 8 | 9 | describe('CRBTC', function () { 10 | let root, nonRoot, accounts; 11 | let cToken; 12 | beforeEach(async () => { 13 | [root, nonRoot, ...accounts] = saddle.accounts; 14 | cToken = await makeCToken({kind: 'crbtc', comptrollerOpts: {kind: 'bool'}}); 15 | }); 16 | 17 | describe("getCashPrior", () => { 18 | it("returns the amount of ether held by the cRBTC contract before the current message", async () => { 19 | expect(await call(cToken, 'harnessGetCashPrior', [], {value: 100})).toEqualNumber(0); 20 | }); 21 | }); 22 | 23 | describe("doTransferIn", () => { 24 | it("succeeds if from is msg.nonRoot and amount is msg.value", async () => { 25 | expect(await call(cToken, 'harnessDoTransferIn', [root, 100], {value: 100})).toEqualNumber(100); 26 | }); 27 | 28 | it("reverts if from != msg.sender", async () => { 29 | await expect(call(cToken, 'harnessDoTransferIn', [nonRoot, 100], {value: 100})).rejects.toRevert("revert R7"); 30 | }); 31 | 32 | it("reverts if amount != msg.value", async () => { 33 | await expect(call(cToken, 'harnessDoTransferIn', [root, 77], {value: 100})).rejects.toRevert("revert R10"); 34 | }); 35 | 36 | describe("doTransferOut", () => { 37 | it("transfers ether out", async () => { 38 | const beforeBalances = await getBalances([cToken], [nonRoot]); 39 | const receipt = await send(cToken, 'harnessDoTransferOut', [nonRoot, 77], {value: 77}); 40 | const afterBalances = await getBalances([cToken], [nonRoot]); 41 | expect(receipt).toSucceed(); 42 | expect(afterBalances).toEqual(await adjustBalances(beforeBalances, [ 43 | [cToken, nonRoot, 'eth', 77] 44 | ])); 45 | }); 46 | 47 | it("reverts if it fails", async () => { 48 | await expect(call(cToken, 'harnessDoTransferOut', [root, 77], {value: 0})).rejects.toRevert(); 49 | }); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /tests/Tokens/setComptrollerTest.js: -------------------------------------------------------------------------------- 1 | const { 2 | makeComptroller, 3 | makeCToken 4 | } = require('../Utils/Compound'); 5 | 6 | describe('CToken', function () { 7 | let root, accounts; 8 | let cToken, oldComptroller, newComptroller; 9 | beforeEach(async () => { 10 | [root, ...accounts] = saddle.accounts; 11 | cToken = await makeCToken(); 12 | oldComptroller = cToken.comptroller; 13 | newComptroller = await makeComptroller(); 14 | expect(newComptroller._address).not.toEqual(oldComptroller._address); 15 | }); 16 | 17 | describe('_setComptroller', () => { 18 | it("should fail if called by non-admin", async () => { 19 | expect( 20 | await send(cToken, '_setComptroller', [newComptroller._address], { from: accounts[0] }) 21 | ).toHaveTokenFailure('UNAUTHORIZED', 'SET_COMPTROLLER_OWNER_CHECK'); 22 | expect(await call(cToken, 'comptroller')).toEqual(oldComptroller._address); 23 | }); 24 | 25 | it("reverts if passed a contract that doesn't implement isComptroller", async () => { 26 | await expect(send(cToken, '_setComptroller', [cToken.underlying._address])).rejects.toRevert("revert"); 27 | expect(await call(cToken, 'comptroller')).toEqual(oldComptroller._address); 28 | }); 29 | 30 | it("reverts if passed a contract that implements isComptroller as false", async () => { 31 | // extremely unlikely to occur, of course, but let's be exhaustive 32 | const badComptroller = await makeComptroller({ kind: 'false-marker' }); 33 | await expect(send(cToken, '_setComptroller', [badComptroller._address])).rejects.toRevert("revert T22"); 34 | expect(await call(cToken, 'comptroller')).toEqual(oldComptroller._address); 35 | }); 36 | 37 | it("updates comptroller and emits log on success", async () => { 38 | const result = await send(cToken, '_setComptroller', [newComptroller._address]); 39 | expect(result).toSucceed(); 40 | expect(result).toHaveLog('NewComptroller', { 41 | oldComptroller: oldComptroller._address, 42 | newComptroller: newComptroller._address 43 | }); 44 | expect(await call(cToken, 'comptroller')).toEqual(newComptroller._address); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/Tokens/transferTest.js: -------------------------------------------------------------------------------- 1 | const {makeCToken} = require('../Utils/Compound'); 2 | 3 | describe('CToken', function () { 4 | let root, accounts; 5 | beforeEach(async () => { 6 | [root, ...accounts] = saddle.accounts; 7 | }); 8 | 9 | describe('transfer', () => { 10 | it("cannot transfer from a zero balance", async () => { 11 | const cToken = await makeCToken({supportMarket: true}); 12 | expect(await call(cToken, 'balanceOf', [root])).toEqualNumber(0); 13 | expect(await send(cToken, 'transfer', [accounts[0], 100])).toHaveTokenFailure('MATH_ERROR', 'TRANSFER_NOT_ENOUGH'); 14 | }); 15 | 16 | it("transfers 50 tokens", async () => { 17 | const cToken = await makeCToken({supportMarket: true}); 18 | await send(cToken, 'harnessSetBalance', [root, 100]); 19 | expect(await call(cToken, 'balanceOf', [root])).toEqualNumber(100); 20 | await send(cToken, 'transfer', [accounts[0], 50]); 21 | expect(await call(cToken, 'balanceOf', [root])).toEqualNumber(50); 22 | expect(await call(cToken, 'balanceOf', [accounts[0]])).toEqualNumber(50); 23 | }); 24 | 25 | it("doesn't transfer when src == dst", async () => { 26 | const cToken = await makeCToken({supportMarket: true}); 27 | await send(cToken, 'harnessSetBalance', [root, 100]); 28 | expect(await call(cToken, 'balanceOf', [root])).toEqualNumber(100); 29 | expect(await send(cToken, 'transfer', [root, 50])).toHaveTokenFailure('BAD_INPUT', 'TRANSFER_NOT_ALLOWED'); 30 | }); 31 | 32 | it("rejects transfer when not allowed and reverts if not verified", async () => { 33 | const cToken = await makeCToken({comptrollerOpts: {kind: 'bool'}}); 34 | await send(cToken, 'harnessSetBalance', [root, 100]); 35 | expect(await call(cToken, 'balanceOf', [root])).toEqualNumber(100); 36 | 37 | await send(cToken.comptroller, 'setTransferAllowed', [false]) 38 | expect(await send(cToken, 'transfer', [root, 50])).toHaveTrollReject('TRANSFER_COMPTROLLER_REJECTION'); 39 | 40 | await send(cToken.comptroller, 'setTransferAllowed', [true]) 41 | await send(cToken.comptroller, 'setTransferVerify', [false]) 42 | // no longer support verifyTransfer on cToken end 43 | // await expect(send(cToken, 'transfer', [accounts[0], 50])).rejects.toRevert("revert transferVerify rejected transfer"); 44 | }); 45 | }); 46 | }); -------------------------------------------------------------------------------- /tests/Tropykus/kSATWithdrawAllFundsTest.js: -------------------------------------------------------------------------------- 1 | const { etherMantissa, etherBalance, etherGasCost, etherUnsigned } = require("../Utils/Ethereum"); 2 | const { makeCToken, makeComptroller, fastForward } = require('../Utils/Compound'); 3 | 4 | describe('kSAT Market protected against all liquidity withdrawal', () => { 5 | beforeEach(async () => { 6 | [root, alice, bob] = saddle.accounts; 7 | comptroller = await makeComptroller({ 8 | kind: 'unitroller-g6', 9 | closeFactor: 0.5, 10 | liquidationIncentive: 0.07, 11 | }); 12 | kSAT = await makeCToken({ 13 | name: 'kSAT', 14 | kind: 'crbtc', 15 | comptroller, 16 | interestRateModelOpts: { 17 | kind: 'hurricane', 18 | }, 19 | exchangeRate: 0.02, 20 | supportMarket: true, 21 | underlyingPrice: 52050, 22 | collateralFactor: 0.5, 23 | }); 24 | kRBTC = await makeCToken({ 25 | name: 'kRBTC', 26 | kind: 'crbtc', 27 | comptroller, 28 | interestRateModelOpts: { 29 | kind: 'white-paper', 30 | }, 31 | exchangeRate: 0.02, 32 | supportMarket: true, 33 | underlyingPrice: 52050, 34 | collateralFactor: 0.5 35 | }); 36 | markets = [kRBTC, kSAT]; 37 | expect(await send(kSAT, 'addSubsidy', { from: root, value: etherMantissa(1) })).toSucceed(); 38 | await send(comptroller, 'enterMarkets', [markets.map(mkt => mkt._address)], { from: root }); 39 | await send(kRBTC, 'mint', { from: root, value: etherMantissa(10) }); 40 | expect(await send(kRBTC, 'borrow', [etherMantissa(3)])).toSucceed(); 41 | }); 42 | it('Should allow a lender to mint after other lenders withdraw with utilization', async () => { 43 | expect(await send(kSAT, 'mint', { from: root, value: etherMantissa(0.025) })).toSucceed(); 44 | expect(await send(kSAT, 'mint', { from: alice, value: etherMantissa(0.025) })).toSucceed(); 45 | await send(comptroller, 'enterMarkets', [markets.map(mkt => mkt._address)], { from: bob }); 46 | expect(await send(kRBTC, 'mint', { from: bob, value: etherMantissa(1) })).toSucceed(); 47 | expect(await send(kSAT, 'borrow', [etherMantissa(0.01)], { from: bob })).toSucceed(); 48 | fastForward(kSAT, 2880); 49 | fastForward(kRBTC, 2880); 50 | await send(comptroller, 'fastForward', [2880]); 51 | expect(await send(kSAT, 'redeem', [etherMantissa(1.25)], { from: alice })).toSucceed(); 52 | expect(await send(kSAT, 'redeem', [etherMantissa(1.25)], { from: root })).toSucceed(); 53 | expect(await send(kSAT, 'mint', { from: root, value: etherMantissa(0.025) })).toSucceed(); 54 | }); 55 | }); -------------------------------------------------------------------------------- /tests/Utils/InfuraProxy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node --max-old-space-size=32768 2 | 3 | // Very Dumb Proxy 4 | // - will slow you down for uncached responses 5 | // - will crash when it runs out of memory (although now runs with 32GB heap by default) 6 | // - does not handle block number in url gracefully yet 7 | // 8 | // Run the proxy server e.g.: 9 | // $ tests/Utils/InfuraProxy.js 10 | // 11 | // Replace in Web3Fork command, or use from curl e.g.: 12 | // $ curl -X POST localhost:1337/kovan/367b617143a94994b4f9c20e36c31839 \ 13 | // --data "{\"jsonrpc\": \"2.0\", \"method\": \"eth_blockNumber\", \"params\": [], \"id\": 1}" 14 | // {"jsonrpc":"2.0","id":1,"result":"0x10675d7"} 15 | 16 | const http = require('http'); 17 | const https = require('https'); 18 | 19 | const port = 1337; 20 | const server = http.createServer(handle).listen(port); 21 | const cache = {}; 22 | 23 | async function handle(req, res) { 24 | let data = '' 25 | req.on('data', (d) => data += d); 26 | req.on('end', async () => { 27 | const [network, project] = req.url.substr(1).split('/'); 28 | const query = JSON.parse(data), id = query.id; delete query['id']; 29 | const key = `${req.url}:${JSON.stringify(query)}`; 30 | const hit = cache[key]; 31 | if (hit) { 32 | console.log('Cache hit...', network, req.method, project, data); 33 | const reply = JSON.parse(hit); reply.id = id; 34 | res.writeHead(200, {'Content-Type': 'application/javascript'}); 35 | res.end(JSON.stringify(reply)); 36 | } else { 37 | try { 38 | console.log('Requesting...', network, req.method, project, data); 39 | const result = await fetch({ 40 | host: `${network}.infura.io`, 41 | method: req.method, 42 | path: `/v3/${project}`, 43 | data: data 44 | }); 45 | res.writeHead(200, {'Content-Type': 'application/javascript'}); 46 | res.end(cache[key] = result); 47 | } catch (e) { 48 | console.error(e) 49 | res.writeHead(500, {'Content-Type': 'application/javascript'}); 50 | res.end(JSON.stringify({error: 'request failed'})); 51 | } 52 | } 53 | }); 54 | } 55 | 56 | async function fetch(options) { 57 | let data = '' 58 | return new Promise( 59 | (okay, fail) => { 60 | const req = https.request(options, (res) => { 61 | res.on('data', (d) => data += d); 62 | res.on('end', () => okay(data)); 63 | }); 64 | req.on('error', (e) => fail(e)); 65 | req.end(options.data); 66 | }); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /tests/Utils/JS.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function dfn(val, def) { 4 | return isFinite(val) ? val : def; 5 | } 6 | 7 | function last(elems) { 8 | return Array.isArray(elems) ? elems[elems.length - 1] : elems; 9 | } 10 | 11 | function lookup(obj, path = []) { 12 | return Array.isArray(path) ? path.reduce((a, k) => a[k], obj) : obj[path]; 13 | } 14 | 15 | function select(obj, keys = []) { 16 | return keys.reduce((a, k) => (a[k] = obj[k], a), {}) 17 | } 18 | 19 | module.exports = { 20 | dfn, 21 | last, 22 | lookup, 23 | select 24 | }; 25 | --------------------------------------------------------------------------------