├── .circleci └── config.yml ├── .gitattributes ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── LICENSE ├── Makefile ├── README.md ├── contracts ├── CollateralLocker.sol ├── CollateralLockerFactory.sol ├── DebtLocker.sol ├── DebtLockerFactory.sol ├── FundingLocker.sol ├── FundingLockerFactory.sol ├── LateFeeCalc.sol ├── LiquidityLocker.sol ├── LiquidityLockerFactory.sol ├── Loan.sol ├── LoanFactory.sol ├── MapleGlobals.sol ├── MapleTreasury.sol ├── MplRewards.sol ├── MplRewardsFactory.sol ├── Pool.sol ├── PoolFactory.sol ├── PremiumCalc.sol ├── RepaymentCalc.sol ├── StakeLocker.sol ├── StakeLockerFactory.sol ├── interfaces │ ├── IBFactory.sol │ ├── IBPool.sol │ ├── ICollateralLocker.sol │ ├── ICollateralLockerFactory.sol │ ├── IDebtLocker.sol │ ├── IDebtLockerFactory.sol │ ├── IERC20Details.sol │ ├── IERC2258.sol │ ├── IFundingLocker.sol │ ├── IFundingLockerFactory.sol │ ├── ILateFeeCalc.sol │ ├── ILiquidityLocker.sol │ ├── ILiquidityLockerFactory.sol │ ├── ILoan.sol │ ├── ILoanFactory.sol │ ├── IMapleGlobals.sol │ ├── IMapleToken.sol │ ├── IMapleTreasury.sol │ ├── IMplRewards.sol │ ├── IMplRewardsFactory.sol │ ├── IOracle.sol │ ├── IPool.sol │ ├── IPoolFactory.sol │ ├── IPremiumCalc.sol │ ├── IRepaymentCalc.sol │ ├── IStakeLocker.sol │ ├── IStakeLockerFactory.sol │ ├── ISubFactory.sol │ └── IUniswapRouter.sol ├── library │ ├── LoanLib.sol │ ├── PoolLib.sol │ └── Util.sol ├── math │ ├── SafeMathInt.sol │ └── SafeMathUint.sol ├── oracles │ ├── ChainlinkOracle.sol │ ├── IChainlinkAggregatorV3.sol │ └── UsdOracle.sol ├── test │ ├── Calcs.t.sol │ ├── ChainLinkOracle.t.sol │ ├── CollateralLockerFactory.t.sol │ ├── DebtLockerFactory.t.sol │ ├── FundingLockerFactory.t.sol │ ├── Gulp.t.sol │ ├── Loan.t.sol │ ├── LoanFactory.t.sol │ ├── LoanLiquidation.t.sol │ ├── MapleGlobals.t.sol │ ├── MapleTreasury.t.sol │ ├── MplRewards.t.sol │ ├── MplRewardsFactory.t.sol │ ├── PoolClaim.t.sol │ ├── PoolCustodial.t.sol │ ├── PoolDelegate.t.sol │ ├── PoolExcess.t.sol │ ├── PoolFDTLosses.t.sol │ ├── PoolFactory.t.sol │ ├── PoolLiquidation.t.sol │ ├── PoolLiquidityProvider.t.sol │ ├── StakeLocker.t.sol │ ├── StakeLockerCustodial.t.sol │ ├── StakeLockerFactory.t.sol │ ├── TestUtil.sol │ ├── interfaces │ │ ├── IUniswapV2Factory.sol │ │ ├── IUniswapV2Pair.sol │ │ └── IUniswapV2Router02.sol │ └── user │ │ ├── Borrower.sol │ │ ├── Commoner.sol │ │ ├── Custodian.sol │ │ ├── EmergencyAdmin.sol │ │ ├── Farmer.sol │ │ ├── Governor.sol │ │ ├── Holder.sol │ │ ├── LP.sol │ │ ├── Lender.sol │ │ ├── PoolDelegate.sol │ │ ├── SecurityAdmin.sol │ │ └── Staker.sol └── token │ ├── BasicFDT.sol │ ├── ExtendedFDT.sol │ ├── LoanFDT.sol │ ├── PoolFDT.sol │ ├── StakeLockerFDT.sol │ └── interfaces │ ├── IBaseFDT.sol │ ├── IBasicFDT.sol │ ├── IExtendedFDT.sol │ ├── ILoanFDT.sol │ ├── IPoolFDT.sol │ └── IStakeLockerFDT.sol ├── flatten.sh ├── flattened-contracts ├── ChainlinkOracle.sol ├── CollateralLocker.sol ├── CollateralLockerFactory.sol ├── DebtLocker.sol ├── DebtLockerFactory.sol ├── FundingLocker.sol ├── FundingLockerFactory.sol ├── IChainlinkAggregatorV3.sol ├── LateFeeCalc.sol ├── LiquidityLocker.sol ├── LiquidityLockerFactory.sol ├── Loan.sol ├── LoanFactory.sol ├── LoanLib.sol ├── MapleGlobals.sol ├── MapleTreasury.sol ├── MplRewards.sol ├── MplRewardsFactory.sol ├── Pool.sol ├── PoolFactory.sol ├── PoolLib.sol ├── PremiumCalc.sol ├── RepaymentCalc.sol ├── StakeLocker.sol ├── StakeLockerFactory.sol ├── UsdOracle.sol └── Util.sol ├── test-ci.sh └── test.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | gcp-cli: circleci/gcp-cli@1 5 | 6 | aliases: 7 | - &defaults 8 | docker: 9 | - image: circleci/node:14 10 | 11 | jobs: 12 | dapp_build: 13 | docker: 14 | - image: bakii0499/dapptools:0.1.0 15 | steps: 16 | - restore_cache: 17 | key: nix-deps-02 18 | - run: 19 | name: Checkout maple-core 20 | command: | 21 | GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" git clone git@github.com:maple-labs/maple-core.git . 22 | git checkout $CIRCLE_BRANCH 23 | - run: 24 | name: Build contracts 25 | command: | 26 | GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" dapp update 27 | export DAPP_LINK_TEST_LIBRARIES=1 28 | DAPP_SRC="contracts" SOLC_FLAGS="--optimize --optimize-runs 200" dapp --use solc:0.6.11 build 29 | - persist_to_workspace: 30 | root: ~/project 31 | paths: 32 | - "./*" 33 | - save_cache: 34 | paths: 35 | - /nix/deps/02 # location depends on npm version 36 | key: nix-deps-02 37 | 38 | dapp_test: 39 | docker: 40 | - image: bakii0499/dapptools:0.1.0 41 | parallelism: 20 42 | steps: 43 | - attach_workspace: 44 | # Must be absolute path or relative path from working_directory 45 | at: ~/project 46 | - restore_cache: 47 | key: nix-deps-02 48 | - run: 49 | name: Run unit tests 50 | command: | 51 | cd ~/project 52 | TEST=$(circleci tests glob contracts/test/*.t.sol | circleci tests split --split-by=name) 53 | export ETH_RPC_URL=$ETH_RPC_URL_ALCHEMY 54 | ./test-ci.sh $TEST 55 | no_output_timeout: 60m 56 | 57 | integration_test_dependencies: 58 | <<: *defaults 59 | steps: 60 | - run: 61 | name: Checkout maple-core 62 | command: | 63 | GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" git clone git@github.com:maple-labs/maple-core.git 64 | cd ~/project/maple-core 65 | git checkout $CIRCLE_BRANCH 66 | GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" git submodule update --init --recursive 67 | - run: 68 | name: Checkout maple-deploy 69 | command: | 70 | cd ~/project 71 | GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" git clone git@github.com:maple-labs/maple-deploy.git 72 | - restore_cache: 73 | key: v1-npm-deps-{{ checksum "~/project/maple-deploy/package.json" }} 74 | - run: 75 | name: Install maple-deploy deps 76 | command: cd ~/project/maple-deploy && yarn 77 | - save_cache: 78 | key: v1-npm-deps-{{ checksum "~/project/maple-deploy/package.json" }} 79 | paths: 80 | - ~/project/maple-deploy/node_modules 81 | - run: 82 | name: Setup maple-deploy .env 83 | command: | 84 | cd ~/project/maple-deploy 85 | echo "HARDHAT_MNEMONIC=$HARDHAT_MNEMONIC" >> .env 86 | echo "HARDHAT_FORK_RPC_URL=$HARDHAT_FORK_RPC_URL" >> .env 87 | echo "HARDHAT_FORK_BLOCK_HEIGHT=11780000" >> .env 88 | echo "NETWORK=localhost" >> .env 89 | - run: 90 | name: Setup maple-deploy symlinks 91 | command: | 92 | ln -s ../../../maple-core/contracts ~/project/maple-deploy/hardhat/contracts/core 93 | ln -s ../../maple-core/lib ~/project/maple-deploy/hardhat/lib 94 | - persist_to_workspace: 95 | root: ~/project 96 | paths: 97 | - "./*" 98 | 99 | check_size: 100 | <<: *defaults 101 | steps: 102 | - attach_workspace: 103 | at: ~/project 104 | - run: 105 | name: Check contract sizes 106 | command: cd ~/project/maple-deploy && yarn compile && yarn size 107 | 108 | integration_test: 109 | <<: *defaults 110 | steps: 111 | - attach_workspace: 112 | at: ~/project 113 | - run: 114 | name: Start testchain and run hardhat waffle tests 115 | command: cd ~/project/maple-deploy && ./test-ci.sh 116 | 117 | deploy_kaleido: 118 | <<: *defaults 119 | steps: 120 | - attach_workspace: 121 | # Must be absolute path or relative path from working_directory 122 | at: ~/project 123 | - run: 124 | name: Update PATH and Define Environment Variable at Runtime 125 | command: | 126 | echo 'export MAPLE_KALEIDO_URL=$MAPLE_KALEIDO_URL' >> $BASH_ENV 127 | echo 'export MAPLE_MNEMONIC=$MAPLE_MNEMONIC' >> $BASH_ENV 128 | echo 'export NETWORK="kaleido"' >> $BASH_ENV 129 | echo 'export NODE_ENV="private"' >> $BASH_ENV 130 | source $BASH_ENV 131 | - run: 132 | name: Deploy contracts to Kaleido 133 | command: cd ~/project && yarn deploy && yarn test 134 | - persist_to_workspace: 135 | root: ~/project 136 | paths: 137 | - "./*" 138 | 139 | upload_kaleido_artifacts_master: 140 | executor: 141 | name: gcp-cli/google 142 | steps: 143 | - attach_workspace: 144 | # Must be absolute path or relative path from working_directory 145 | at: ~/project 146 | - gcp-cli/initialize 147 | - run: 148 | name: Upload files to gcloud bucket 149 | command: | 150 | gsutil -m rsync -r ~/project/packages/contracts/private gs://maple-artifacts/contract-artifacts/kaleido/$CIRCLE_BUILD_NUM 151 | gsutil -m rsync -r ~/project/packages/contracts/private gs://maple-artifacts/contract-artifacts/kaleido/current 152 | 153 | upload_kaleido_artifacts_develop: 154 | executor: 155 | name: gcp-cli/google 156 | steps: 157 | - attach_workspace: 158 | # Must be absolute path or relative path from working_directory 159 | at: ~/project 160 | - gcp-cli/initialize 161 | - run: 162 | name: Upload files to gcloud bucket 163 | command: | 164 | gsutil -m rsync -r ~/project/packages/contracts/private gs://maple-artifacts/contract-artifacts/kaleido/develop 165 | 166 | workflows: 167 | version: 2 168 | test_all: 169 | jobs: 170 | - integration_test_dependencies: 171 | context: hardhat 172 | - check_size: 173 | context: hardhat 174 | requires: 175 | - integration_test_dependencies 176 | # - integration_test: 177 | # context: hardhat 178 | # requires: 179 | # - integration_test_dependencies 180 | 181 | - dapp_build: 182 | context: seth 183 | - dapp_test: 184 | context: seth 185 | requires: 186 | - dapp_build 187 | 188 | # - deploy_kaleido: 189 | # context: kaleido 190 | # requires: 191 | # - dapp_test 192 | # - integration_test 193 | # filters: 194 | # branches: 195 | # only: 196 | # - master 197 | # - develop 198 | # - upload_kaleido_artifacts_master: 199 | # context: google-cloud 200 | # requires: 201 | # - deploy_kaleido 202 | # filters: 203 | # branches: 204 | # only: 205 | # - master 206 | # - upload_kaleido_artifacts_develop: 207 | # context: google-cloud 208 | # requires: 209 | # - deploy_kaleido 210 | # filters: 211 | # branches: 212 | # only: 213 | # - develop 214 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | # Integrations Checklist 4 | 5 | - [ ] Have any function signatures changed? If yes, outline below. 6 | - [ ] Have any features changed or been added? If yes, outline below. 7 | - [ ] Have any events changed or been added? If yes, outline below. 8 | - [ ] Has all documentation been updated? 9 | 10 | # Changelog 11 | ## Function Signature Changes 12 | 13 | ## Features 14 | 15 | ## Events 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | key.json 4 | .env 5 | 6 | # DappTools 7 | hevm* 8 | /out 9 | /cache 10 | 11 | # Flattened functions 12 | /flattened-contracts/functions 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/maple-labs/ds-test 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/Openzeppelin/openzeppelin-contracts 7 | [submodule "cache/dapp-cache"] 8 | path = cache/dapp-cache 9 | url = git@github.com:maple-labs/maple-cache 10 | [submodule "module/maple-token"] 11 | path = module/maple-token 12 | url = git@github.com:maple-labs/maple-token.git 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.compileUsingRemoteVersion": "v0.7.0+commit.9e61f92b", 3 | "solidity.enableLocalNodeCompiler": false 4 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all :; DAPP_SRC=contracts DAPP_BUILD_OPTIMIZE=1 DAPP_BUILD_OPTIMIZE_RUNS=200 dapp --use solc:0.6.11 build 2 | clean :; dapp clean 3 | flat :; ./flatten.sh 4 | test :; ./test.sh 5 | ci-test :; ./test-ci.sh 6 | -------------------------------------------------------------------------------- /contracts/CollateralLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol"; 5 | 6 | /// @title CollateralLocker holds custody of Collateral Asset for Loans. 7 | contract CollateralLocker { 8 | 9 | using SafeERC20 for IERC20; 10 | 11 | IERC20 public immutable collateralAsset; // Address the Collateral Asset the Loan is collateralized with. 12 | address public immutable loan; // Loan contract address this CollateralLocker is attached to. 13 | 14 | constructor(address _collateralAsset, address _loan) public { 15 | collateralAsset = IERC20(_collateralAsset); 16 | loan = _loan; 17 | } 18 | 19 | /** 20 | @dev Checks that `msg.sender` is the Loan. 21 | */ 22 | modifier isLoan() { 23 | require(msg.sender == loan, "CL:NOT_L"); 24 | _; 25 | } 26 | 27 | /** 28 | @dev Transfers amount of Collateral Asset to a destination account. Only the Loan can call this function. 29 | @param dst Destination to transfer Collateral Asset to. 30 | @param amt Amount of Collateral Asset to transfer. 31 | */ 32 | function pull(address dst, uint256 amt) isLoan external { 33 | collateralAsset.safeTransfer(dst, amt); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /contracts/CollateralLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./interfaces/ILoanFactory.sol"; 5 | 6 | import "./CollateralLocker.sol"; 7 | 8 | /// @title CollateralLockerFactory instantiates CollateralLockers. 9 | contract CollateralLockerFactory { 10 | 11 | mapping(address => address) public owner; // Mapping of CollateralLocker addresses to their owner (i.e owner[locker] = Owner of the CollateralLocker). 12 | mapping(address => bool) public isLocker; // True only if a CollateralLocker was created by this factory. 13 | 14 | uint8 public constant factoryType = 0; // i.e FactoryType::COLLATERAL_LOCKER_FACTORY 15 | 16 | event CollateralLockerCreated(address indexed owner, address collateralLocker, address collateralAsset); 17 | 18 | /** 19 | @dev Instantiates a CollateralLocker. 20 | @dev It emits a `CollateralLockerCreated` event. 21 | @param collateralAsset The Collateral Asset this CollateralLocker will escrow. 22 | @return collateralLocker Address of the instantiated CollateralLocker. 23 | */ 24 | function newLocker(address collateralAsset) external returns (address collateralLocker) { 25 | collateralLocker = address(new CollateralLocker(collateralAsset, msg.sender)); 26 | owner[collateralLocker] = msg.sender; 27 | isLocker[collateralLocker] = true; 28 | 29 | emit CollateralLockerCreated(msg.sender, collateralLocker, collateralAsset); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /contracts/DebtLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/math/SafeMath.sol"; 5 | import "lib/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | import "./interfaces/ILoan.sol"; 8 | 9 | /// @title DebtLocker holds custody of LoanFDT tokens. 10 | contract DebtLocker { 11 | 12 | using SafeMath for uint256; 13 | using SafeERC20 for IERC20; 14 | 15 | uint256 constant WAD = 10 ** 18; 16 | 17 | ILoan public immutable loan; // The Loan contract this locker is holding tokens for. 18 | IERC20 public immutable liquidityAsset; // The Liquidity Asset this locker can claim. 19 | address public immutable pool; // The owner of this Locker (the Pool). 20 | 21 | uint256 public lastPrincipalPaid; // Loan total principal paid at last time claim() was called. 22 | uint256 public lastInterestPaid; // Loan total interest paid at last time claim() was called. 23 | uint256 public lastFeePaid; // Loan total fees paid at last time claim() was called. 24 | uint256 public lastExcessReturned; // Loan total excess returned at last time claim() was called. 25 | uint256 public lastDefaultSuffered; // Loan total default suffered at last time claim() was called. 26 | uint256 public lastAmountRecovered; // Liquidity Asset (a.k.a. loan asset) recovered from liquidation of Loan collateral. 27 | 28 | /** 29 | @dev Checks that `msg.sender` is the Pool. 30 | */ 31 | modifier isPool() { 32 | require(msg.sender == pool, "DL:NOT_P"); 33 | _; 34 | } 35 | 36 | constructor(address _loan, address _pool) public { 37 | loan = ILoan(_loan); 38 | pool = _pool; 39 | liquidityAsset = IERC20(ILoan(_loan).liquidityAsset()); 40 | } 41 | 42 | // Note: If newAmt > 0, totalNewAmt will always be greater than zero. 43 | function _calcAllotment(uint256 newAmt, uint256 totalClaim, uint256 totalNewAmt) internal pure returns (uint256) { 44 | return newAmt == uint256(0) ? uint256(0) : newAmt.mul(totalClaim).div(totalNewAmt); 45 | } 46 | 47 | /** 48 | @dev Claims funds distribution for Loan via LoanFDT. Only the Pool can call this function. 49 | @return [0] = Total Claimed 50 | [1] = Interest Claimed 51 | [2] = Principal Claimed 52 | [3] = Pool Delegate Fee Claimed 53 | [4] = Excess Returned Claimed 54 | [5] = Amount Recovered (from Liquidation) 55 | [6] = Default Suffered 56 | */ 57 | function claim() external isPool returns (uint256[7] memory) { 58 | 59 | uint256 newDefaultSuffered = uint256(0); 60 | uint256 loan_defaultSuffered = loan.defaultSuffered(); 61 | 62 | // If a default has occurred, update storage variable and update memory variable from zero for return. 63 | // `newDefaultSuffered` represents the proportional loss that the DebtLocker registers based on its balance 64 | // of LoanFDTs in comparison to the total supply of LoanFDTs. 65 | // Default will occur only once, so below statement will only be `true` once. 66 | if (lastDefaultSuffered == uint256(0) && loan_defaultSuffered > uint256(0)) { 67 | newDefaultSuffered = lastDefaultSuffered = _calcAllotment(loan.balanceOf(address(this)), loan_defaultSuffered, loan.totalSupply()); 68 | } 69 | 70 | // Account for any transfers into Loan that have occurred since last call. 71 | loan.updateFundsReceived(); 72 | 73 | // Handles case where no claimable funds are present but a default must be registered (zero-collateralized loans defaulting). 74 | if (loan.withdrawableFundsOf(address(this)) == uint256(0)) return([0, 0, 0, 0, 0, 0, newDefaultSuffered]); 75 | 76 | // If there are claimable funds, calculate portions and claim using LoanFDT. 77 | 78 | // Calculate payment deltas. 79 | uint256 newInterest = loan.interestPaid() - lastInterestPaid; // `loan.interestPaid` updated in `loan._makePayment()` 80 | uint256 newPrincipal = loan.principalPaid() - lastPrincipalPaid; // `loan.principalPaid` updated in `loan._makePayment()` 81 | 82 | // Update storage variables for next delta calculation. 83 | lastInterestPaid = loan.interestPaid(); 84 | lastPrincipalPaid = loan.principalPaid(); 85 | 86 | // Calculate one-time deltas if storage variables have not yet been updated. 87 | uint256 newFee = lastFeePaid == uint256(0) ? loan.feePaid() : uint256(0); // `loan.feePaid` updated in `loan.drawdown()` 88 | uint256 newExcess = lastExcessReturned == uint256(0) ? loan.excessReturned() : uint256(0); // `loan.excessReturned` updated in `loan.unwind()` OR `loan.drawdown()` if `amt < fundingLockerBal` 89 | uint256 newAmountRecovered = lastAmountRecovered == uint256(0) ? loan.amountRecovered() : uint256(0); // `loan.amountRecovered` updated in `loan.triggerDefault()` 90 | 91 | // Update DebtLocker storage variables if Loan storage variables has been updated since last claim. 92 | if (newFee > 0) lastFeePaid = newFee; 93 | if (newExcess > 0) lastExcessReturned = newExcess; 94 | if (newAmountRecovered > 0) lastAmountRecovered = newAmountRecovered; 95 | 96 | // Withdraw all claimable funds via LoanFDT. 97 | uint256 beforeBal = liquidityAsset.balanceOf(address(this)); // Current balance of DebtLocker (accounts for direct inflows). 98 | loan.withdrawFunds(); // Transfer funds from Loan to DebtLocker. 99 | uint256 claimBal = liquidityAsset.balanceOf(address(this)).sub(beforeBal); // Amount claimed from Loan using LoanFDT. 100 | 101 | // Calculate sum of all deltas, to be used to calculate portions for metadata. 102 | uint256 sum = newInterest.add(newPrincipal).add(newFee).add(newExcess).add(newAmountRecovered); 103 | 104 | // Calculate payment portions based on LoanFDT claim. 105 | newInterest = _calcAllotment(newInterest, claimBal, sum); 106 | newPrincipal = _calcAllotment(newPrincipal, claimBal, sum); 107 | 108 | // Calculate one-time portions based on LoanFDT claim. 109 | newFee = _calcAllotment(newFee, claimBal, sum); 110 | newExcess = _calcAllotment(newExcess, claimBal, sum); 111 | newAmountRecovered = _calcAllotment(newAmountRecovered, claimBal, sum); 112 | 113 | liquidityAsset.safeTransfer(pool, claimBal); // Transfer entire amount claimed using LoanFDT. 114 | 115 | // Return claim amount plus all relevant metadata, to be used by Pool for further claim logic. 116 | // Note: newInterest + newPrincipal + newFee + newExcess + newAmountRecovered = claimBal - dust 117 | // The dust on the right side of the equation gathers in the pool after transfers are made. 118 | return([claimBal, newInterest, newPrincipal, newFee, newExcess, newAmountRecovered, newDefaultSuffered]); 119 | } 120 | 121 | /** 122 | @dev Liquidates a Loan that is held by this contract. Only the Pool can call this function. 123 | */ 124 | function triggerDefault() external isPool { 125 | loan.triggerDefault(); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /contracts/DebtLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./DebtLocker.sol"; 5 | 6 | /// @title DebtLockerFactory instantiates DebtLockers. 7 | contract DebtLockerFactory { 8 | 9 | mapping(address => address) public owner; // Mapping of DebtLocker addresses to their owner (i.e owner[locker] = Owner of the DebtLocker). 10 | mapping(address => bool) public isLocker; // True only if a DebtLocker was created by this factory. 11 | 12 | uint8 public constant factoryType = 1; // i.e LockerFactoryTypes::DEBT_LOCKER_FACTORY 13 | 14 | event DebtLockerCreated(address indexed owner, address debtLocker, address loan); 15 | 16 | /** 17 | @dev Instantiates a DebtLocker. 18 | @dev It emits a `DebtLockerCreated` event. 19 | @param loan The Loan this DebtLocker will escrow tokens for. 20 | @return debtLocker Address of the instantiated DebtLocker. 21 | */ 22 | function newLocker(address loan) external returns (address debtLocker) { 23 | debtLocker = address(new DebtLocker(loan, msg.sender)); 24 | owner[debtLocker] = msg.sender; 25 | isLocker[debtLocker] = true; 26 | 27 | emit DebtLockerCreated(msg.sender, debtLocker, loan); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /contracts/FundingLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol"; 5 | 6 | /// @title FundingLocker holds custody of Liquidity Asset tokens during the funding period of a Loan. 7 | contract FundingLocker { 8 | 9 | using SafeERC20 for IERC20; 10 | 11 | IERC20 public immutable liquidityAsset; // Asset the Loan was funded with. 12 | address public immutable loan; // Loan this FundingLocker has funded. 13 | 14 | constructor(address _liquidityAsset, address _loan) public { 15 | liquidityAsset = IERC20(_liquidityAsset); 16 | loan = _loan; 17 | } 18 | 19 | /** 20 | @dev Checks that `msg.sender` is the Loan. 21 | */ 22 | modifier isLoan() { 23 | require(msg.sender == loan, "FL:NOT_L"); 24 | _; 25 | } 26 | 27 | /** 28 | @dev Transfers amount of Liquidity Asset to a destination account. Only the Loan can call this function. 29 | @param dst Destination to transfer Liquidity Asset to. 30 | @param amt Amount of Liquidity Asset to transfer. 31 | */ 32 | function pull(address dst, uint256 amt) isLoan external { 33 | liquidityAsset.safeTransfer(dst, amt); 34 | } 35 | 36 | /** 37 | @dev Transfers entire amount of Liquidity Asset held in escrow to the Loan. Only the Loan can call this function. 38 | */ 39 | function drain() isLoan external { 40 | uint256 amt = liquidityAsset.balanceOf(address(this)); 41 | liquidityAsset.safeTransfer(loan, amt); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /contracts/FundingLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./FundingLocker.sol"; 5 | 6 | import "./interfaces/ILoanFactory.sol"; 7 | 8 | /// @title FundingLockerFactory instantiates FundingLockers. 9 | contract FundingLockerFactory { 10 | 11 | mapping(address => address) public owner; // Mapping of FundingLocker addresses to their owner (i.e owner[locker] = Owner of the FundingLocker). 12 | mapping(address => bool) public isLocker; // True only if a FundingLocker was created by this factory. 13 | 14 | uint8 public constant factoryType = 2; // i.e FactoryType::FUNDING_LOCKER_FACTORY 15 | 16 | event FundingLockerCreated(address indexed owner, address fundingLocker, address liquidityAsset); 17 | 18 | /** 19 | @dev Instantiates a FundingLocker. 20 | @dev It emits a `FundingLockerCreated` event. 21 | @param liquidityAsset The Liquidity Asset this FundingLocker will escrow. 22 | @return fundingLocker Address of the instantiated FundingLocker. 23 | */ 24 | function newLocker(address liquidityAsset) external returns (address fundingLocker) { 25 | fundingLocker = address(new FundingLocker(liquidityAsset, msg.sender)); 26 | owner[fundingLocker] = msg.sender; 27 | isLocker[fundingLocker] = true; 28 | 29 | emit FundingLockerCreated(msg.sender, fundingLocker, liquidityAsset); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /contracts/LateFeeCalc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/math/SafeMath.sol"; 5 | 6 | import "./interfaces/ILoan.sol"; 7 | import "./interfaces/IRepaymentCalc.sol"; 8 | 9 | /// @title LateFeeCalc calculates late fees on Loans. 10 | contract LateFeeCalc { 11 | 12 | using SafeMath for uint256; 13 | 14 | uint8 public constant calcType = 11; // "LATEFEE type" 15 | bytes32 public constant name = "FLAT"; 16 | 17 | uint256 public immutable lateFee; // The fee in basis points, charged on the payment amount. 18 | 19 | constructor(uint256 _lateFee) public { 20 | lateFee = _lateFee; 21 | } 22 | 23 | /** 24 | @dev Calculates the late fee payment for a Loan. 25 | @param interest Amount of interest to be used to calculate late fee for. 26 | @return Late fee that is charged to the Borrower. 27 | */ 28 | function getLateFee(uint256 interest) external view returns (uint256) { 29 | return interest.mul(lateFee).div(10_000); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /contracts/LiquidityLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol"; 5 | 6 | import "./interfaces/ILoan.sol"; 7 | 8 | /// @title LiquidityLocker holds custody of Liquidity Asset tokens for a given Pool. 9 | contract LiquidityLocker { 10 | 11 | using SafeERC20 for IERC20; 12 | 13 | address public immutable pool; // The Pool that owns this LiquidityLocker. 14 | IERC20 public immutable liquidityAsset; // The Liquidity Asset which this LiquidityLocker will escrow. 15 | 16 | constructor(address _liquidityAsset, address _pool) public { 17 | liquidityAsset = IERC20(_liquidityAsset); 18 | pool = _pool; 19 | } 20 | 21 | /** 22 | @dev Checks that `msg.sender` is the Pool. 23 | */ 24 | modifier isPool() { 25 | require(msg.sender == pool, "LL:NOT_P"); 26 | _; 27 | } 28 | 29 | /** 30 | @dev Transfers amount of Liquidity Asset to a destination account. Only the Pool can call this function. 31 | @param dst Destination to transfer Liquidity Asset to. 32 | @param amt Amount of Liquidity Asset to transfer. 33 | */ 34 | function transfer(address dst, uint256 amt) external isPool { 35 | require(dst != address(0), "LL:NULL_DST"); 36 | liquidityAsset.safeTransfer(dst, amt); 37 | } 38 | 39 | /** 40 | @dev Funds a Loan using available assets in this LiquidityLocker. Only the Pool can call this function. 41 | @param loan The Loan to fund. 42 | @param debtLocker The DebtLocker that will escrow debt tokens. 43 | @param amt Amount of Liquidity Asset to fund the Loan for. 44 | */ 45 | function fundLoan(address loan, address debtLocker, uint256 amt) external isPool { 46 | liquidityAsset.safeApprove(loan, amt); 47 | ILoan(loan).fundLoan(debtLocker, amt); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /contracts/LiquidityLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./LiquidityLocker.sol"; 5 | 6 | /// @title LiquidityLockerFactory instantiates LiquidityLockers. 7 | contract LiquidityLockerFactory { 8 | 9 | mapping(address => address) public owner; // Mapping of LiquidityLocker addresses to their owner (i.e owner[locker] = Owner of the LiquidityLocker). 10 | mapping(address => bool) public isLocker; // True only if a LiquidityLocker was created by this factory. 11 | 12 | uint8 public constant factoryType = 3; // i.e LockerFactoryTypes::LIQUIDITY_LOCKER_FACTORY 13 | 14 | event LiquidityLockerCreated(address indexed owner, address liquidityLocker, address liquidityAsset); 15 | 16 | /** 17 | @dev Instantiates a LiquidityLocker contract. 18 | @dev It emits a `LiquidityLockerCreated` event. 19 | @param liquidityAsset The Liquidity Asset this LiquidityLocker will escrow. 20 | @return liquidityLocker Address of the instantiated LiquidityLocker. 21 | */ 22 | function newLocker(address liquidityAsset) external returns (address liquidityLocker) { 23 | liquidityLocker = address(new LiquidityLocker(liquidityAsset, msg.sender)); 24 | owner[liquidityLocker] = msg.sender; 25 | isLocker[liquidityLocker] = true; 26 | 27 | emit LiquidityLockerCreated(msg.sender, liquidityLocker, liquidityAsset); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /contracts/LoanFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/utils/Pausable.sol"; 5 | 6 | import "./Loan.sol"; 7 | 8 | /// @title LoanFactory instantiates Loans. 9 | contract LoanFactory is Pausable { 10 | 11 | using SafeMath for uint256; 12 | 13 | uint8 public constant CL_FACTORY = 0; // Factory type of `CollateralLockerFactory`. 14 | uint8 public constant FL_FACTORY = 2; // Factory type of `FundingLockerFactory`. 15 | 16 | uint8 public constant INTEREST_CALC_TYPE = 10; // Calc type of `RepaymentCalc`. 17 | uint8 public constant LATEFEE_CALC_TYPE = 11; // Calc type of `LateFeeCalc`. 18 | uint8 public constant PREMIUM_CALC_TYPE = 12; // Calc type of `PremiumCalc`. 19 | 20 | IMapleGlobals public globals; // Instance of the MapleGlobals. 21 | 22 | uint256 public loansCreated; // Incrementor for number of Loans created. 23 | 24 | mapping(uint256 => address) public loans; // Loans address mapping. 25 | mapping(address => bool) public isLoan; // True only if a Loan was created by this factory. 26 | 27 | mapping(address => bool) public loanFactoryAdmins; // The LoanFactory Admin addresses that have permission to do certain operations in case of disaster management. 28 | 29 | event LoanFactoryAdminSet(address indexed loanFactoryAdmin, bool allowed); 30 | 31 | event LoanCreated( 32 | address loan, 33 | address indexed borrower, 34 | address indexed liquidityAsset, 35 | address collateralAsset, 36 | address collateralLocker, 37 | address fundingLocker, 38 | uint256[5] specs, 39 | address[3] calcs, 40 | string name, 41 | string symbol 42 | ); 43 | 44 | constructor(address _globals) public { 45 | globals = IMapleGlobals(_globals); 46 | } 47 | 48 | /** 49 | @dev Sets MapleGlobals. Only the Governor can call this function. 50 | @param newGlobals Address of new MapleGlobals. 51 | */ 52 | function setGlobals(address newGlobals) external { 53 | _isValidGovernor(); 54 | globals = IMapleGlobals(newGlobals); 55 | } 56 | 57 | /** 58 | @dev Create a new Loan. 59 | @dev It emits a `LoanCreated` event. 60 | @param liquidityAsset Asset the Loan will raise funding in. 61 | @param collateralAsset Asset the Loan will use as collateral. 62 | @param flFactory The factory to instantiate a FundingLocker from. 63 | @param clFactory The factory to instantiate a CollateralLocker from. 64 | @param specs Contains specifications for this Loan. 65 | specs[0] = apr 66 | specs[1] = termDays 67 | specs[2] = paymentIntervalDays 68 | specs[3] = requestAmount 69 | specs[4] = collateralRatio 70 | @param calcs The calculators used for this Loan. 71 | calcs[0] = repaymentCalc 72 | calcs[1] = lateFeeCalc 73 | calcs[2] = premiumCalc 74 | @return loanAddress Address of the instantiated Loan. 75 | */ 76 | function createLoan( 77 | address liquidityAsset, 78 | address collateralAsset, 79 | address flFactory, 80 | address clFactory, 81 | uint256[5] memory specs, 82 | address[3] memory calcs 83 | ) external whenNotPaused returns (address loanAddress) { 84 | _whenProtocolNotPaused(); 85 | IMapleGlobals _globals = globals; 86 | 87 | // Perform validity checks. 88 | require(_globals.isValidSubFactory(address(this), flFactory, FL_FACTORY), "LF:INVALID_FLF"); 89 | require(_globals.isValidSubFactory(address(this), clFactory, CL_FACTORY), "LF:INVALID_CLF"); 90 | 91 | require(_globals.isValidCalc(calcs[0], INTEREST_CALC_TYPE), "LF:INVALID_INT_C"); 92 | require(_globals.isValidCalc(calcs[1], LATEFEE_CALC_TYPE), "LF:INVALID_LATE_FEE_C"); 93 | require(_globals.isValidCalc(calcs[2], PREMIUM_CALC_TYPE), "LF:INVALID_PREM_C"); 94 | 95 | // Deploy new Loan. 96 | Loan loan = new Loan( 97 | msg.sender, 98 | liquidityAsset, 99 | collateralAsset, 100 | flFactory, 101 | clFactory, 102 | specs, 103 | calcs 104 | ); 105 | 106 | // Update the LoanFactory identification mappings. 107 | loanAddress = address(loan); 108 | loans[loansCreated] = loanAddress; 109 | isLoan[loanAddress] = true; 110 | ++loansCreated; 111 | 112 | emit LoanCreated( 113 | loanAddress, 114 | msg.sender, 115 | liquidityAsset, 116 | collateralAsset, 117 | loan.collateralLocker(), 118 | loan.fundingLocker(), 119 | specs, 120 | calcs, 121 | loan.name(), 122 | loan.symbol() 123 | ); 124 | } 125 | 126 | /** 127 | @dev Sets a LoanFactory Admin. Only the Governor can call this function. 128 | @dev It emits a `LoanFactoryAdminSet` event. 129 | @param loanFactoryAdmin An address being allowed or disallowed as a LoanFactory Admin. 130 | @param allowed Status of a LoanFactory Admin. 131 | */ 132 | function setLoanFactoryAdmin(address loanFactoryAdmin, bool allowed) external { 133 | _isValidGovernor(); 134 | loanFactoryAdmins[loanFactoryAdmin] = allowed; 135 | emit LoanFactoryAdminSet(loanFactoryAdmin, allowed); 136 | } 137 | 138 | /** 139 | @dev Triggers paused state. Halts functionality for certain functions. Only the Governor or a LoanFactory Admin can call this function. 140 | */ 141 | function pause() external { 142 | _isValidGovernorOrLoanFactoryAdmin(); 143 | super._pause(); 144 | } 145 | 146 | /** 147 | @dev Triggers unpaused state. Restores functionality for certain functions. Only the Governor or a LoanFactory Admin can call this function. 148 | */ 149 | function unpause() external { 150 | _isValidGovernorOrLoanFactoryAdmin(); 151 | super._unpause(); 152 | } 153 | 154 | /** 155 | @dev Checks that `msg.sender` is the Governor. 156 | */ 157 | function _isValidGovernor() internal view { 158 | require(msg.sender == globals.governor(), "LF:NOT_GOV"); 159 | } 160 | 161 | /** 162 | @dev Checks that `msg.sender` is the Governor or a LoanFactory Admin. 163 | */ 164 | function _isValidGovernorOrLoanFactoryAdmin() internal view { 165 | require(msg.sender == globals.governor() || loanFactoryAdmins[msg.sender], "LF:NOT_GOV_OR_ADMIN"); 166 | } 167 | 168 | /** 169 | @dev Checks that the protocol is not in a paused state. 170 | */ 171 | function _whenProtocolNotPaused() internal view { 172 | require(!globals.protocolPaused(), "LF:PROTO_PAUSED"); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /contracts/MapleTreasury.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/math/SafeMath.sol"; 5 | import "lib/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | import "./interfaces/IMapleGlobals.sol"; 8 | import "./interfaces/IMapleToken.sol"; 9 | import "./interfaces/IERC20Details.sol"; 10 | import "./interfaces/IUniswapRouter.sol"; 11 | 12 | import "./library/Util.sol"; 13 | 14 | /// @title MapleTreasury earns revenue from Loans and distributes it to token holders and the Maple development team. 15 | contract MapleTreasury { 16 | 17 | using SafeMath for uint256; 18 | using SafeERC20 for IERC20; 19 | 20 | address public immutable mpl; // The address of ERC-2222 Maple Token for the Maple protocol. 21 | address public immutable fundsToken; // The address of the `fundsToken` of the ERC-2222 Maple Token. 22 | address public immutable uniswapRouter; // The address of the official UniswapV2 router. 23 | address public globals; // The address of an instance of MapleGlobals. 24 | 25 | /** 26 | @dev Instantiates the MapleTreasury contract. 27 | @param _mpl The address of ERC-2222 Maple Token for the Maple protocol. 28 | @param _fundsToken The address of the `fundsToken` of the ERC-2222 Maple Token. 29 | @param _uniswapRouter The address of the official UniswapV2 router. 30 | @param _globals The address of an instance of MapleGlobals. 31 | */ 32 | constructor( 33 | address _mpl, 34 | address _fundsToken, 35 | address _uniswapRouter, 36 | address _globals 37 | ) public { 38 | mpl = _mpl; 39 | fundsToken = _fundsToken; 40 | uniswapRouter = _uniswapRouter; 41 | globals = _globals; 42 | } 43 | 44 | event ERC20Conversion(address indexed asset, uint256 amountIn, uint256 amountOut); 45 | event DistributedToHolders(uint256 amount); 46 | event ERC20Reclaimed(address indexed asset, uint256 amount); 47 | event GlobalsSet(address newGlobals); 48 | 49 | /** 50 | @dev Checks that `msg.sender` is the Governor. 51 | */ 52 | modifier isGovernor() { 53 | require(msg.sender == IMapleGlobals(globals).governor(), "MT:NOT_GOV"); 54 | _; 55 | } 56 | 57 | /** 58 | @dev Updates the MapleGlobals instance. Only the Governor can call this function. 59 | @dev It emits a `GlobalsSet` event. 60 | @param newGlobals Address of a new MapleGlobals instance. 61 | */ 62 | function setGlobals(address newGlobals) isGovernor external { 63 | globals = newGlobals; 64 | emit GlobalsSet(newGlobals); 65 | } 66 | 67 | /** 68 | @dev Reclaims Treasury funds to the MapleDAO address. Only the Governor can call this function. 69 | @dev It emits a `ERC20Reclaimed` event. 70 | @param asset Address of the token to be reclaimed. 71 | @param amount Amount to withdraw. 72 | */ 73 | function reclaimERC20(address asset, uint256 amount) isGovernor external { 74 | IERC20(asset).safeTransfer(msg.sender, amount); 75 | emit ERC20Reclaimed(asset, amount); 76 | } 77 | 78 | /** 79 | @dev Passes through the current `fundsToken` balance of the Treasury to Maple Token, where it can be claimed by MPL holders. 80 | Only the Governor can call this function. 81 | @dev It emits a `DistributedToHolders` event. 82 | */ 83 | function distributeToHolders() isGovernor external { 84 | IERC20 _fundsToken = IERC20(fundsToken); 85 | uint256 distributeAmount = _fundsToken.balanceOf(address(this)); 86 | _fundsToken.safeTransfer(mpl, distributeAmount); 87 | IMapleToken(mpl).updateFundsReceived(); 88 | emit DistributedToHolders(distributeAmount); 89 | } 90 | 91 | /** 92 | @dev Converts an ERC-20 asset, via Uniswap, to `fundsToken`. Only the Governor can call this function. 93 | @dev It emits a `ERC20Conversion` event. 94 | @param asset The ERC-20 asset to convert to `fundsToken`. 95 | */ 96 | function convertERC20(address asset) isGovernor external { 97 | require(asset != fundsToken, "MT:ASSET_IS_FUNDS_TOKEN"); 98 | 99 | IMapleGlobals _globals = IMapleGlobals(globals); 100 | 101 | uint256 assetBalance = IERC20(asset).balanceOf(address(this)); 102 | uint256 minAmount = Util.calcMinAmount(_globals, asset, fundsToken, assetBalance); 103 | 104 | IERC20(asset).safeApprove(uniswapRouter, uint256(0)); 105 | IERC20(asset).safeApprove(uniswapRouter, assetBalance); 106 | 107 | address uniswapAssetForPath = _globals.defaultUniswapPath(asset, fundsToken); 108 | bool middleAsset = uniswapAssetForPath != fundsToken && uniswapAssetForPath != address(0); 109 | 110 | address[] memory path = new address[](middleAsset ? 3 : 2); 111 | 112 | path[0] = asset; 113 | path[1] = middleAsset ? uniswapAssetForPath : fundsToken; 114 | 115 | if (middleAsset) path[2] = fundsToken; 116 | 117 | uint256[] memory returnAmounts = IUniswapRouter(uniswapRouter).swapExactTokensForTokens( 118 | assetBalance, 119 | minAmount.sub(minAmount.mul(_globals.maxSwapSlippage()).div(10_000)), 120 | path, 121 | address(this), 122 | block.timestamp 123 | ); 124 | 125 | emit ERC20Conversion(asset, returnAmounts[0], returnAmounts[path.length - 1]); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /contracts/MplRewards.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/access/Ownable.sol"; 5 | import "lib/openzeppelin-contracts/contracts/math/Math.sol"; 6 | import "lib/openzeppelin-contracts/contracts/math/SafeMath.sol"; 7 | import "lib/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol"; 8 | 9 | import "./interfaces/IERC2258.sol"; 10 | 11 | // https://docs.synthetix.io/contracts/source/contracts/stakingrewards 12 | /// @title MplRewards Synthetix farming contract fork for liquidity mining. 13 | contract MplRewards is Ownable { 14 | 15 | using SafeMath for uint256; 16 | using SafeERC20 for IERC20; 17 | 18 | IERC20 public immutable rewardsToken; 19 | IERC2258 public immutable stakingToken; 20 | 21 | uint256 public periodFinish; 22 | uint256 public rewardRate; 23 | uint256 public rewardsDuration; 24 | uint256 public lastUpdateTime; 25 | uint256 public rewardPerTokenStored; 26 | uint256 public lastPauseTime; 27 | bool public paused; 28 | 29 | mapping(address => uint256) public userRewardPerTokenPaid; 30 | mapping(address => uint256) public rewards; 31 | 32 | uint256 private _totalSupply; 33 | 34 | mapping(address => uint256) private _balances; 35 | 36 | event RewardAdded(uint256 reward); 37 | event Staked(address indexed account, uint256 amount); 38 | event Withdrawn(address indexed account, uint256 amount); 39 | event RewardPaid(address indexed account, uint256 reward); 40 | event RewardsDurationUpdated(uint256 newDuration); 41 | event Recovered(address token, uint256 amount); 42 | event PauseChanged(bool isPaused); 43 | 44 | constructor(address _rewardsToken, address _stakingToken, address _owner) public { 45 | rewardsToken = IERC20(_rewardsToken); 46 | stakingToken = IERC2258(_stakingToken); 47 | rewardsDuration = 7 days; 48 | transferOwnership(_owner); 49 | } 50 | 51 | function _updateReward(address account) internal { 52 | uint256 _rewardPerTokenStored = rewardPerToken(); 53 | rewardPerTokenStored = _rewardPerTokenStored; 54 | lastUpdateTime = lastTimeRewardApplicable(); 55 | 56 | if (account != address(0)) { 57 | rewards[account] = earned(account); 58 | userRewardPerTokenPaid[account] = _rewardPerTokenStored; 59 | } 60 | } 61 | 62 | function _notPaused() internal view { 63 | require(!paused, "R:PAUSED"); 64 | } 65 | 66 | function totalSupply() external view returns (uint256) { 67 | return _totalSupply; 68 | } 69 | 70 | function balanceOf(address account) external view returns (uint256) { 71 | return _balances[account]; 72 | } 73 | 74 | function lastTimeRewardApplicable() public view returns (uint256) { 75 | return Math.min(block.timestamp, periodFinish); 76 | } 77 | 78 | function rewardPerToken() public view returns (uint256) { 79 | return _totalSupply == 0 80 | ? rewardPerTokenStored 81 | : rewardPerTokenStored.add( 82 | lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply) 83 | ); 84 | } 85 | 86 | function earned(address account) public view returns (uint256) { 87 | return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]); 88 | } 89 | 90 | function getRewardForDuration() external view returns (uint256) { 91 | return rewardRate.mul(rewardsDuration); 92 | } 93 | 94 | /** 95 | @dev It emits a `Staked` event. 96 | */ 97 | function stake(uint256 amount) external { 98 | _notPaused(); 99 | _updateReward(msg.sender); 100 | uint256 newBalance = _balances[msg.sender].add(amount); 101 | require(amount > 0, "R:ZERO_STAKE"); 102 | require(stakingToken.custodyAllowance(msg.sender, address(this)) >= newBalance, "R:INSUF_CUST_ALLOWANCE"); 103 | _totalSupply = _totalSupply.add(amount); 104 | _balances[msg.sender] = newBalance; 105 | emit Staked(msg.sender, amount); 106 | } 107 | 108 | /** 109 | @dev It emits a `Withdrawn` event. 110 | */ 111 | function withdraw(uint256 amount) public { 112 | _notPaused(); 113 | _updateReward(msg.sender); 114 | require(amount > 0, "R:ZERO_WITHDRAW"); 115 | _totalSupply = _totalSupply.sub(amount); 116 | _balances[msg.sender] = _balances[msg.sender].sub(amount); 117 | stakingToken.transferByCustodian(msg.sender, msg.sender, amount); 118 | emit Withdrawn(msg.sender, amount); 119 | } 120 | 121 | /** 122 | @dev It emits a `RewardPaid` event if any rewards are received. 123 | */ 124 | function getReward() public { 125 | _notPaused(); 126 | _updateReward(msg.sender); 127 | uint256 reward = rewards[msg.sender]; 128 | 129 | if (reward == uint256(0)) return; 130 | 131 | rewards[msg.sender] = uint256(0); 132 | rewardsToken.safeTransfer(msg.sender, reward); 133 | emit RewardPaid(msg.sender, reward); 134 | } 135 | 136 | function exit() external { 137 | withdraw(_balances[msg.sender]); 138 | getReward(); 139 | } 140 | 141 | /** 142 | @dev Only the contract Owner may call this. 143 | @dev It emits a `RewardAdded` event. 144 | */ 145 | function notifyRewardAmount(uint256 reward) external onlyOwner { 146 | _updateReward(address(0)); 147 | 148 | uint256 _rewardRate = block.timestamp >= periodFinish 149 | ? reward.div(rewardsDuration) 150 | : reward.add( 151 | periodFinish.sub(block.timestamp).mul(rewardRate) 152 | ).div(rewardsDuration); 153 | 154 | rewardRate = _rewardRate; 155 | 156 | // Ensure the provided reward amount is not more than the balance in the contract. 157 | // This keeps the reward rate in the right range, preventing overflows due to 158 | // very high values of rewardRate in the earned and rewardsPerToken functions; 159 | // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow. 160 | uint256 balance = rewardsToken.balanceOf(address(this)); 161 | require(_rewardRate <= balance.div(rewardsDuration), "R:REWARD_TOO_HIGH"); 162 | 163 | lastUpdateTime = block.timestamp; 164 | periodFinish = block.timestamp.add(rewardsDuration); 165 | emit RewardAdded(reward); 166 | } 167 | 168 | /** 169 | @dev End rewards emission earlier. Only the contract Owner may call this. 170 | */ 171 | function updatePeriodFinish(uint256 timestamp) external onlyOwner { 172 | _updateReward(address(0)); 173 | periodFinish = timestamp; 174 | } 175 | 176 | /** 177 | @dev Added to support recovering tokens unintentionally sent to this contract. 178 | Only the contract Owner may call this. 179 | @dev It emits a `Recovered` event. 180 | */ 181 | function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner { 182 | IERC20(tokenAddress).safeTransfer(owner(), tokenAmount); 183 | emit Recovered(tokenAddress, tokenAmount); 184 | } 185 | 186 | /** 187 | @dev Only the contract Owner may call this. 188 | @dev It emits a `RewardsDurationUpdated` event. 189 | */ 190 | function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner { 191 | require(block.timestamp > periodFinish, "R:PERIOD_NOT_FINISHED"); 192 | rewardsDuration = _rewardsDuration; 193 | emit RewardsDurationUpdated(rewardsDuration); 194 | } 195 | 196 | /** 197 | @dev Change the paused state of the contract. Only the contract Owner may call this. 198 | @dev It emits a `PauseChanged` event. 199 | */ 200 | function setPaused(bool _paused) external onlyOwner { 201 | // Ensure we're actually changing the state before we do anything 202 | require(_paused != paused, "R:ALREADY_SET"); 203 | 204 | // Set our paused state. 205 | paused = _paused; 206 | 207 | // If applicable, set the last pause time. 208 | if (_paused) lastPauseTime = block.timestamp; 209 | 210 | // Let everyone know that our pause state has changed. 211 | emit PauseChanged(paused); 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /contracts/MplRewardsFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./interfaces/IMapleGlobals.sol"; 5 | 6 | import "./MplRewards.sol"; 7 | 8 | /// @title MplRewardsFactory instantiates MplRewards contracts. 9 | contract MplRewardsFactory { 10 | 11 | IMapleGlobals public globals; // Instance of MapleGlobals, used to retrieve the current Governor. 12 | 13 | mapping(address => bool) public isMplRewards; // True only if an MplRewards was created by this factory. 14 | 15 | event MplRewardsCreated(address indexed rewardsToken, address indexed stakingToken, address indexed mplRewards, address owner); 16 | 17 | constructor(address _globals) public { 18 | globals = IMapleGlobals(_globals); 19 | } 20 | 21 | /** 22 | @dev Updates the MapleGlobals instance. Only the Governor can call this function. 23 | @param _globals Address of new MapleGlobals contract. 24 | */ 25 | function setGlobals(address _globals) external { 26 | require(msg.sender == globals.governor(), "RF:NOT_GOV"); 27 | globals = IMapleGlobals(_globals); 28 | } 29 | 30 | /** 31 | @dev Instantiates a MplRewards contract. Only the Governor can call this function. 32 | @dev It emits a `MplRewardsCreated` event. 33 | @param rewardsToken Address of the rewards token (will always be MPL). 34 | @param stakingToken Address of the staking token (token used to stake to earn rewards). 35 | (i.e., Pool address for PoolFDT mining, StakeLocker address for staked BPT mining.) 36 | @return mplRewards Address of the instantiated MplRewards. 37 | */ 38 | function createMplRewards(address rewardsToken, address stakingToken) external returns (address mplRewards) { 39 | require(msg.sender == globals.governor(), "RF:NOT_GOV"); 40 | mplRewards = address(new MplRewards(rewardsToken, stakingToken, msg.sender)); 41 | isMplRewards[mplRewards] = true; 42 | 43 | emit MplRewardsCreated(rewardsToken, stakingToken, mplRewards, msg.sender); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /contracts/PoolFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/utils/Pausable.sol"; 5 | 6 | import "./Pool.sol"; 7 | 8 | /// @title PoolFactory instantiates Pools. 9 | contract PoolFactory is Pausable { 10 | 11 | uint8 public constant LL_FACTORY = 3; // Factory type of `LiquidityLockerFactory`. 12 | uint8 public constant SL_FACTORY = 4; // Factory type of `StakeLockerFactory`. 13 | 14 | uint256 public poolsCreated; // Incrementor for number of Pools created. 15 | IMapleGlobals public globals; // A MapleGlobals instance. 16 | 17 | mapping(uint256 => address) public pools; // Map to reference Pools corresponding to their respective indices. 18 | mapping(address => bool) public isPool; // True only if a Pool was instantiated by this factory. 19 | mapping(address => bool) public poolFactoryAdmins; // The PoolFactory Admin addresses that have permission to do certain operations in case of disaster management. 20 | 21 | event PoolFactoryAdminSet(address indexed poolFactoryAdmin, bool allowed); 22 | 23 | event PoolCreated( 24 | address indexed pool, 25 | address indexed delegate, 26 | address liquidityAsset, 27 | address stakeAsset, 28 | address liquidityLocker, 29 | address stakeLocker, 30 | uint256 stakingFee, 31 | uint256 delegateFee, 32 | uint256 liquidityCap, 33 | string name, 34 | string symbol 35 | ); 36 | 37 | constructor(address _globals) public { 38 | globals = IMapleGlobals(_globals); 39 | } 40 | 41 | /** 42 | @dev Sets MapleGlobals instance. Only the Governor can call this function. 43 | @param newGlobals Address of new MapleGlobals. 44 | */ 45 | function setGlobals(address newGlobals) external { 46 | _isValidGovernor(); 47 | globals = IMapleGlobals(newGlobals); 48 | } 49 | 50 | /** 51 | @dev Instantiates a Pool. 52 | @dev It emits a `PoolCreated` event. 53 | @param liquidityAsset The asset escrowed in a LiquidityLocker. 54 | @param stakeAsset The asset escrowed in a StakeLocker. 55 | @param slFactory The factory to instantiate a StakeLocker from. 56 | @param llFactory The factory to instantiate a LiquidityLocker from. 57 | @param stakingFee Fee that Stakers earn on interest, in basis points. 58 | @param delegateFee Fee that the Pool Delegate earns on interest, in basis points. 59 | @param liquidityCap Amount of Liquidity Asset accepted by the Pool. 60 | @return poolAddress Address of the instantiated Pool. 61 | */ 62 | function createPool( 63 | address liquidityAsset, 64 | address stakeAsset, 65 | address slFactory, 66 | address llFactory, 67 | uint256 stakingFee, 68 | uint256 delegateFee, 69 | uint256 liquidityCap 70 | ) external whenNotPaused returns (address poolAddress) { 71 | _whenProtocolNotPaused(); 72 | { 73 | IMapleGlobals _globals = globals; 74 | require(_globals.isValidSubFactory(address(this), llFactory, LL_FACTORY), "PF:INVALID_LLF"); 75 | require(_globals.isValidSubFactory(address(this), slFactory, SL_FACTORY), "PF:INVALID_SLF"); 76 | require(_globals.isValidPoolDelegate(msg.sender), "PF:NOT_DELEGATE"); 77 | } 78 | 79 | string memory name = "Maple Pool Token"; 80 | string memory symbol = "MPL-LP"; 81 | 82 | Pool pool = 83 | new Pool( 84 | msg.sender, 85 | liquidityAsset, 86 | stakeAsset, 87 | slFactory, 88 | llFactory, 89 | stakingFee, 90 | delegateFee, 91 | liquidityCap, 92 | name, 93 | symbol 94 | ); 95 | 96 | poolAddress = address(pool); 97 | pools[poolsCreated] = poolAddress; 98 | isPool[poolAddress] = true; 99 | ++poolsCreated; 100 | 101 | emit PoolCreated( 102 | poolAddress, 103 | msg.sender, 104 | liquidityAsset, 105 | stakeAsset, 106 | pool.liquidityLocker(), 107 | pool.stakeLocker(), 108 | stakingFee, 109 | delegateFee, 110 | liquidityCap, 111 | name, 112 | symbol 113 | ); 114 | } 115 | 116 | /** 117 | @dev Sets a PoolFactory Admin. Only the Governor can call this function. 118 | @dev It emits a `PoolFactoryAdminSet` event. 119 | @param poolFactoryAdmin An address being allowed or disallowed as a PoolFactory Admin. 120 | @param allowed Status of a PoolFactory Admin. 121 | */ 122 | function setPoolFactoryAdmin(address poolFactoryAdmin, bool allowed) external { 123 | _isValidGovernor(); 124 | poolFactoryAdmins[poolFactoryAdmin] = allowed; 125 | emit PoolFactoryAdminSet(poolFactoryAdmin, allowed); 126 | } 127 | 128 | /** 129 | @dev Triggers paused state. Halts functionality for certain functions. Only the Governor or a PoolFactory Admin can call this function. 130 | */ 131 | function pause() external { 132 | _isValidGovernorOrPoolFactoryAdmin(); 133 | super._pause(); 134 | } 135 | 136 | /** 137 | @dev Triggers unpaused state. Restores functionality for certain functions. Only the Governor or a PoolFactory Admin can call this function. 138 | */ 139 | function unpause() external { 140 | _isValidGovernorOrPoolFactoryAdmin(); 141 | super._unpause(); 142 | } 143 | 144 | /** 145 | @dev Checks that `msg.sender` is the Governor. 146 | */ 147 | function _isValidGovernor() internal view { 148 | require(msg.sender == globals.governor(), "PF:NOT_GOV"); 149 | } 150 | 151 | /** 152 | @dev Checks that `msg.sender` is the Governor or a PoolFactory Admin. 153 | */ 154 | function _isValidGovernorOrPoolFactoryAdmin() internal view { 155 | require(msg.sender == globals.governor() || poolFactoryAdmins[msg.sender], "PF:NOT_GOV_OR_ADMIN"); 156 | } 157 | 158 | /** 159 | @dev Checks that the protocol is not in a paused state. 160 | */ 161 | function _whenProtocolNotPaused() internal view { 162 | require(!globals.protocolPaused(), "PF:PROTO_PAUSED"); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /contracts/PremiumCalc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/math/SafeMath.sol"; 5 | 6 | import "./interfaces/ILoan.sol"; 7 | 8 | /// @title PremiumCalc calculates premium fees on Loans. 9 | contract PremiumCalc { 10 | 11 | using SafeMath for uint256; 12 | 13 | uint8 public constant calcType = 12; // PREMIUM type. 14 | bytes32 public constant name = "FLAT"; 15 | 16 | uint256 public immutable premiumFee; // Flat percentage fee (in basis points) of principal to charge as a premium when calling a Loan. 17 | 18 | constructor(uint256 _premiumFee) public { 19 | premiumFee = _premiumFee; 20 | } 21 | 22 | /** 23 | @dev Calculates the premium payment for a Loan, when making a full payment. 24 | @param _loan The address of a Loan to calculate a premium payment for. 25 | @return total Principal + Interest. 26 | @return principalOwed Principal. 27 | @return interest Interest. 28 | */ 29 | function getPremiumPayment(address _loan) external view returns (uint256 total, uint256 principalOwed, uint256 interest) { 30 | principalOwed = ILoan(_loan).principalOwed(); 31 | interest = principalOwed.mul(premiumFee).div(10_000); 32 | total = interest.add(principalOwed); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /contracts/RepaymentCalc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/math/SafeMath.sol"; 5 | 6 | import "./interfaces/ILoan.sol"; 7 | 8 | /// @title RepaymentCalc calculates payment amounts on Loans. 9 | contract RepaymentCalc { 10 | 11 | using SafeMath for uint256; 12 | 13 | uint8 public constant calcType = 10; // INTEREST type. 14 | bytes32 public constant name = "INTEREST_ONLY"; // Calculator. 15 | 16 | /** 17 | @dev Calculates the next payment for a Loan. 18 | @param _loan The address of a Loan to calculate a payment for. 19 | @return total Entitled interest of the next payment (Principal + Interest only when the next payment is last payment of the Loan). 20 | @return principalOwed Entitled principal amount needed to be paid in the next payment. 21 | @return interest Entitled interest amount needed to be paid in the next payment. 22 | */ 23 | function getNextPayment(address _loan) external view returns (uint256 total, uint256 principalOwed, uint256 interest) { 24 | 25 | ILoan loan = ILoan(_loan); 26 | 27 | principalOwed = loan.principalOwed(); 28 | 29 | // Equation = principal * APR * (paymentInterval / year) 30 | // Principal * APR gives annual interest 31 | // Multiplying that by (paymentInterval / year) gives portion of annual interest due for each interval. 32 | interest = 33 | principalOwed 34 | .mul(loan.apr()) 35 | .mul(loan.paymentIntervalSeconds()) 36 | .div(10_000) 37 | .div(365 days); 38 | 39 | (total, principalOwed) = loan.paymentsRemaining() == 1 40 | ? (interest.add(principalOwed), principalOwed) 41 | : (interest, 0); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /contracts/StakeLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./StakeLocker.sol"; 5 | 6 | /// @title StakeLockerFactory instantiates StakeLockers. 7 | contract StakeLockerFactory { 8 | 9 | mapping(address => address) public owner; // Mapping of StakeLocker addresses to their owner (i.e owner[locker] = Owner of the StakeLocker). 10 | mapping(address => bool) public isLocker; // True only if a StakeLocker was created by this factory. 11 | 12 | uint8 public constant factoryType = 4; // i.e FactoryType::STAKE_LOCKER_FACTORY. 13 | 14 | event StakeLockerCreated( 15 | address indexed owner, 16 | address stakeLocker, 17 | address stakeAsset, 18 | address liquidityAsset, 19 | string name, 20 | string symbol 21 | ); 22 | 23 | /** 24 | @dev Instantiate a StakeLocker. 25 | @dev It emits a `StakeLockerCreated` event. 26 | @param stakeAsset Address of the Stake Asset (generally Balancer Pool BPTs). 27 | @param liquidityAsset Address of the Liquidity Asset (as defined in the Pool). 28 | @return stakeLocker Address of the instantiated StakeLocker. 29 | */ 30 | function newLocker( 31 | address stakeAsset, 32 | address liquidityAsset 33 | ) external returns (address stakeLocker) { 34 | stakeLocker = address(new StakeLocker(stakeAsset, liquidityAsset, msg.sender)); 35 | owner[stakeLocker] = msg.sender; 36 | isLocker[stakeLocker] = true; 37 | 38 | emit StakeLockerCreated( 39 | msg.sender, 40 | stakeLocker, 41 | stakeAsset, 42 | liquidityAsset, 43 | StakeLocker(stakeLocker).name(), 44 | StakeLocker(stakeLocker).symbol() 45 | ); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /contracts/interfaces/IBFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IBFactory { 5 | 6 | function isBPool(address) external view returns (bool); 7 | 8 | function newBPool() external returns (address); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/IBPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IBPool { 5 | 6 | function transfer(address, uint256) external returns (bool); 7 | 8 | function INIT_POOL_SUPPLY() external view returns (uint256); 9 | 10 | function MAX_OUT_RATIO() external view returns (uint256); 11 | 12 | function bind(address, uint256, uint256) external; 13 | 14 | function balanceOf(address) external view returns (uint256); 15 | 16 | function finalize() external; 17 | 18 | function gulp(address) external; 19 | 20 | function isFinalized() external view returns (bool); 21 | 22 | function isBound(address) external view returns (bool); 23 | 24 | function getNumTokens() external view returns (uint256); 25 | 26 | function getBalance(address) external view returns (uint256); 27 | 28 | function getNormalizedWeight(address) external view returns (uint256); 29 | 30 | function getDenormalizedWeight(address) external view returns (uint256); 31 | 32 | function getTotalDenormalizedWeight() external view returns (uint256); 33 | 34 | function getSwapFee() external view returns (uint256); 35 | 36 | function totalSupply() external view returns (uint256); 37 | 38 | function getFinalTokens() external view returns (address[] memory); 39 | 40 | function joinPool(uint poolAmountOut, uint[] calldata maxAmountsIn) external; 41 | 42 | function calcSingleOutGivenPoolIn( 43 | uint256 tokenBalanceOut, 44 | uint256 tokenWeightOut, 45 | uint256 poolSupply, 46 | uint256 totalWeight, 47 | uint256 poolAmountIn, 48 | uint256 swapFee 49 | ) external pure returns (uint256); 50 | 51 | function calcPoolInGivenSingleOut( 52 | uint256 tokenBalanceOut, 53 | uint256 tokenWeightOut, 54 | uint256 poolSupply, 55 | uint256 totalWeight, 56 | uint256 tokenAmountOut, 57 | uint256 swapFee 58 | ) external pure returns (uint256); 59 | 60 | function exitswapExternAmountOut( 61 | address tokenOut, 62 | uint256 tokenAmountOut, 63 | uint256 maxPoolAmountIn 64 | ) external returns (uint256 poolAmountIn); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /contracts/interfaces/ICollateralLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface ICollateralLocker { 5 | 6 | function collateralAsset() external view returns (address); 7 | 8 | function loan() external view returns (address); 9 | 10 | function pull(address, uint256) external; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/ICollateralLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface ICollateralLockerFactory { 5 | 6 | function owner(address) external view returns (address); 7 | 8 | function isLocker(address) external view returns (bool); 9 | 10 | function factoryType() external view returns (uint8); 11 | 12 | function newLocker(address) external returns (address); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IDebtLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IDebtLocker { 5 | 6 | function loan() external view returns (address); 7 | 8 | function liquidityAsset() external view returns (address); 9 | 10 | function pool() external view returns (address); 11 | 12 | function lastPrincipalPaid() external view returns (uint256); 13 | 14 | function lastInterestPaid() external view returns (uint256); 15 | 16 | function lastFeePaid() external view returns (uint256); 17 | 18 | function lastExcessReturned() external view returns (uint256); 19 | 20 | function lastDefaultSuffered() external view returns (uint256); 21 | 22 | function lastAmountRecovered() external view returns (uint256); 23 | 24 | function claim() external returns (uint256[7] memory); 25 | 26 | function triggerDefault() external; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /contracts/interfaces/IDebtLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IDebtLockerFactory { 5 | 6 | function owner(address) external view returns (address); 7 | 8 | function isLocker(address) external view returns (bool); 9 | 10 | function factoryType() external view returns (uint8); 11 | 12 | function newLocker(address) external returns (address); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC20Details.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 5 | 6 | interface IERC20Details is IERC20 { 7 | 8 | function name() external view returns (string memory); 9 | 10 | function symbol() external view returns (string memory); 11 | 12 | function decimals() external view returns (uint256); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC2258.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IERC2258 { 5 | 6 | // Increase the custody limit of a custodian either directly or via signed authorization 7 | function increaseCustodyAllowance(address custodian, uint256 amount) external; 8 | 9 | // Query individual custody limit and total custody limit across all custodians 10 | function custodyAllowance(address account, address custodian) external view returns (uint256); 11 | function totalCustodyAllowance(address account) external view returns (uint256); 12 | 13 | // Allows a custodian to exercise their right to transfer custodied tokens 14 | function transferByCustodian(address account, address receiver, uint256 amount) external; 15 | 16 | // Custody Events 17 | event CustodyTransfer(address custodian, address from, address to, uint256 amount); 18 | event CustodyAllowanceChanged(address account, address custodian, uint256 oldAllowance, uint256 newAllowance); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /contracts/interfaces/IFundingLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IFundingLocker { 5 | 6 | function liquidityAsset() external view returns (address); 7 | 8 | function loan() external view returns (address); 9 | 10 | function pull(address, uint256) external; 11 | 12 | function drain() external; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IFundingLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IFundingLockerFactory { 5 | 6 | function owner(address) external view returns (address); 7 | 8 | function isLocker(address) external view returns (bool); 9 | 10 | function factoryType() external view returns (uint8); 11 | 12 | function newLocker(address) external returns (address); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/ILateFeeCalc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface ILateFeeCalc { 5 | 6 | function calcType() external view returns (uint8); 7 | 8 | function name() external view returns (bytes32); 9 | 10 | function lateFee() external view returns (uint256); 11 | 12 | function getLateFee(uint256) external view returns (uint256); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/ILiquidityLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface ILiquidityLocker { 5 | 6 | function pool() external view returns (address); 7 | 8 | function liquidityAsset() external view returns (address); 9 | 10 | function transfer(address, uint256) external; 11 | 12 | function fundLoan(address, address, uint256) external; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/ILiquidityLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface ILiquidityLockerFactory { 5 | 6 | function owner(address) external view returns (address); 7 | 8 | function isLocker(address) external view returns (bool); 9 | 10 | function factoryType() external view returns (uint8); 11 | 12 | function newLocker(address) external returns (address); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/ILoan.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "../token/interfaces/ILoanFDT.sol"; 5 | 6 | interface ILoan is ILoanFDT { 7 | 8 | // State Variables 9 | function liquidityAsset() external view returns (address); 10 | 11 | function collateralAsset() external view returns (address); 12 | 13 | function fundingLocker() external view returns (address); 14 | 15 | function flFactory() external view returns (address); 16 | 17 | function collateralLocker() external view returns (address); 18 | 19 | function clFactory() external view returns (address); 20 | 21 | function borrower() external view returns (address); 22 | 23 | function repaymentCalc() external view returns (address); 24 | 25 | function lateFeeCalc() external view returns (address); 26 | 27 | function premiumCalc() external view returns (address); 28 | 29 | function loanState() external view returns (uint256); 30 | 31 | function collateralRequiredForDrawdown(uint256) external view returns (uint256); 32 | 33 | 34 | // Loan Specifications 35 | function apr() external view returns (uint256); 36 | 37 | function paymentsRemaining() external view returns (uint256); 38 | 39 | function paymentIntervalSeconds() external view returns (uint256); 40 | 41 | function requestAmount() external view returns (uint256); 42 | 43 | function collateralRatio() external view returns (uint256); 44 | 45 | function fundingPeriod() external view returns (uint256); 46 | 47 | function defaultGracePeriod() external view returns (uint256); 48 | 49 | function createdAt() external view returns (uint256); 50 | 51 | function principalOwed() external view returns (uint256); 52 | 53 | function principalPaid() external view returns (uint256); 54 | 55 | function interestPaid() external view returns (uint256); 56 | 57 | function feePaid() external view returns (uint256); 58 | 59 | function excessReturned() external view returns (uint256); 60 | 61 | function getNextPayment() external view returns (uint256, uint256, uint256, uint256); 62 | 63 | function superFactory() external view returns (address); 64 | 65 | function termDays() external view returns (uint256); 66 | 67 | function nextPaymentDue() external view returns (uint256); 68 | 69 | function getFullPayment() external view returns (uint256, uint256, uint256); 70 | 71 | 72 | // Liquidations 73 | function amountLiquidated() external view returns (uint256); 74 | 75 | function defaultSuffered() external view returns (uint256); 76 | 77 | function amountRecovered() external view returns (uint256); 78 | 79 | function getExpectedAmountRecovered() external view returns (uint256); 80 | 81 | function liquidationExcess() external view returns (uint256); 82 | 83 | 84 | // Functions 85 | function fundLoan(address, uint256) external; 86 | 87 | function makePayment() external; 88 | 89 | function drawdown(uint256) external; 90 | 91 | function makeFullPayment() external; 92 | 93 | function triggerDefault() external; 94 | 95 | function unwind() external; 96 | 97 | 98 | // Security 99 | function pause() external; 100 | 101 | function unpause() external; 102 | 103 | function loanAdmins(address) external view returns (address); 104 | 105 | function setLoanAdmin(address, bool) external; 106 | 107 | 108 | // Misc 109 | function reclaimERC20(address) external; 110 | 111 | } 112 | -------------------------------------------------------------------------------- /contracts/interfaces/ILoanFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface ILoanFactory { 5 | 6 | function CL_FACTORY() external view returns (uint8); 7 | 8 | function FL_FACTORY() external view returns (uint8); 9 | 10 | function INTEREST_CALC_TYPE() external view returns (uint8); 11 | 12 | function LATEFEE_CALC_TYPE() external view returns (uint8); 13 | 14 | function PREMIUM_CALC_TYPE() external view returns (uint8); 15 | 16 | function globals() external view returns (address); 17 | 18 | function loansCreated() external view returns (uint256); 19 | 20 | function loans(uint256) external view returns (address); 21 | 22 | function isLoan(address) external view returns (bool); 23 | 24 | function loanFactoryAdmins(address) external view returns (bool); 25 | 26 | function setGlobals(address) external; 27 | 28 | function createLoan(address, address, address, address, uint256[5] memory, address[3] memory) external returns (address); 29 | 30 | function setLoanFactoryAdmin(address, bool) external; 31 | 32 | function pause() external; 33 | 34 | function unpause() external; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /contracts/interfaces/IMapleGlobals.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IMapleGlobals { 5 | 6 | function pendingGovernor() external view returns (address); 7 | 8 | function governor() external view returns (address); 9 | 10 | function globalAdmin() external view returns (address); 11 | 12 | function mpl() external view returns (address); 13 | 14 | function mapleTreasury() external view returns (address); 15 | 16 | function isValidBalancerPool(address) external view returns (bool); 17 | 18 | function treasuryFee() external view returns (uint256); 19 | 20 | function investorFee() external view returns (uint256); 21 | 22 | function defaultGracePeriod() external view returns (uint256); 23 | 24 | function fundingPeriod() external view returns (uint256); 25 | 26 | function swapOutRequired() external view returns (uint256); 27 | 28 | function isValidLiquidityAsset(address) external view returns (bool); 29 | 30 | function isValidCollateralAsset(address) external view returns (bool); 31 | 32 | function isValidPoolDelegate(address) external view returns (bool); 33 | 34 | function validCalcs(address) external view returns (bool); 35 | 36 | function isValidCalc(address, uint8) external view returns (bool); 37 | 38 | function getLpCooldownParams() external view returns (uint256, uint256); 39 | 40 | function isValidLoanFactory(address) external view returns (bool); 41 | 42 | function isValidSubFactory(address, address, uint8) external view returns (bool); 43 | 44 | function isValidPoolFactory(address) external view returns (bool); 45 | 46 | function getLatestPrice(address) external view returns (uint256); 47 | 48 | function defaultUniswapPath(address, address) external view returns (address); 49 | 50 | function minLoanEquity() external view returns (uint256); 51 | 52 | function maxSwapSlippage() external view returns (uint256); 53 | 54 | function protocolPaused() external view returns (bool); 55 | 56 | function stakerCooldownPeriod() external view returns (uint256); 57 | 58 | function lpCooldownPeriod() external view returns (uint256); 59 | 60 | function stakerUnstakeWindow() external view returns (uint256); 61 | 62 | function lpWithdrawWindow() external view returns (uint256); 63 | 64 | function oracleFor(address) external view returns (address); 65 | 66 | function validSubFactories(address, address) external view returns (bool); 67 | 68 | function setStakerCooldownPeriod(uint256) external; 69 | 70 | function setLpCooldownPeriod(uint256) external; 71 | 72 | function setStakerUnstakeWindow(uint256) external; 73 | 74 | function setLpWithdrawWindow(uint256) external; 75 | 76 | function setMaxSwapSlippage(uint256) external; 77 | 78 | function setGlobalAdmin(address) external; 79 | 80 | function setValidBalancerPool(address, bool) external; 81 | 82 | function setProtocolPause(bool) external; 83 | 84 | function setValidPoolFactory(address, bool) external; 85 | 86 | function setValidLoanFactory(address, bool) external; 87 | 88 | function setValidSubFactory(address, address, bool) external; 89 | 90 | function setDefaultUniswapPath(address, address, address) external; 91 | 92 | function setPoolDelegateAllowlist(address, bool) external; 93 | 94 | function setCollateralAsset(address, bool) external; 95 | 96 | function setLiquidityAsset(address, bool) external; 97 | 98 | function setCalc(address, bool) external; 99 | 100 | function setInvestorFee(uint256) external; 101 | 102 | function setTreasuryFee(uint256) external; 103 | 104 | function setMapleTreasury(address) external; 105 | 106 | function setDefaultGracePeriod(uint256) external; 107 | 108 | function setMinLoanEquity(uint256) external; 109 | 110 | function setFundingPeriod(uint256) external; 111 | 112 | function setSwapOutRequired(uint256) external; 113 | 114 | function setPriceOracle(address, address) external; 115 | 116 | function setPendingGovernor(address) external; 117 | 118 | function acceptGovernor() external; 119 | 120 | } 121 | -------------------------------------------------------------------------------- /contracts/interfaces/IMapleToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IMapleToken { 5 | 6 | function updateFundsReceived() external; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IMapleTreasury.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IMapleTreasury { 5 | 6 | function mpl() external view returns (address); 7 | 8 | function fundsToken() external view returns (address); 9 | 10 | function uniswapRouter() external view returns (address); 11 | 12 | function globals() external view returns (address); 13 | 14 | function setGlobals(address) external; 15 | 16 | function reclaimERC20(address, uint256) external; 17 | 18 | function distributeToHolders() external; 19 | 20 | function convertERC20(address) external; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /contracts/interfaces/IMplRewards.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IMplRewards { 5 | 6 | // Views 7 | function rewardsToken() external view returns (address); 8 | 9 | function stakingToken() external view returns (address); 10 | 11 | function periodFinish() external view returns (uint256); 12 | 13 | function rewardRate() external view returns (uint256); 14 | 15 | function rewardsDuration() external view returns (uint256); 16 | 17 | function lastUpdateTime() external view returns (uint256); 18 | 19 | function rewardPerTokenStored() external view returns (uint256); 20 | 21 | function lastPauseTime() external view returns (uint256); 22 | 23 | function paused() external view returns (bool); 24 | 25 | function userRewardPerTokenPaid(address) external view returns (uint256); 26 | 27 | function rewards(address) external view returns (uint256); 28 | 29 | function totalSupply() external view returns (uint256); 30 | 31 | function balanceOf(address) external view returns (uint256); 32 | 33 | function lastTimeRewardApplicable() external view returns (uint256); 34 | 35 | function rewardPerToken() external view returns (uint256); 36 | 37 | function earned(address) external view returns (uint256); 38 | 39 | function getRewardForDuration() external view returns (uint256); 40 | 41 | 42 | // Mutative 43 | function stake(uint256) external; 44 | 45 | function withdraw(uint256) external; 46 | 47 | function getReward() external; 48 | 49 | function exit() external; 50 | 51 | function notifyRewardAmount(uint256) external; 52 | 53 | function updatePeriodFinish(uint256) external; 54 | 55 | function recoverERC20(address, uint256) external; 56 | 57 | function setRewardsDuration(uint256) external; 58 | 59 | function setPaused(bool) external; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /contracts/interfaces/IMplRewardsFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IMplRewardsFactory { 5 | 6 | function globals() external view returns (address); 7 | 8 | function isMplRewards(address) external view returns (bool); 9 | 10 | function setGlobals(address _globals) external; 11 | 12 | function createMplRewards(address rewardsToken, address stakingToken) external returns (address mplRewards); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IOracle { 5 | 6 | function priceFeed() external view returns (address); 7 | 8 | function globals() external view returns (address); 9 | 10 | function assetAddress() external view returns (address); 11 | 12 | function manualOverride() external view returns (bool); 13 | 14 | function manualPrice() external view returns (int256); 15 | 16 | function getLatestPrice() external view returns (int256); 17 | 18 | function changeAggregator(address) external; 19 | 20 | function getAssetAddress() external view returns (address); 21 | 22 | function getDenomination() external view returns (bytes32); 23 | 24 | function setManualPrice(int256) external; 25 | 26 | function setManualOverride(bool) external; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /contracts/interfaces/IPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "../token/interfaces/IPoolFDT.sol"; 5 | 6 | interface IPool is IPoolFDT { 7 | 8 | function poolDelegate() external view returns (address); 9 | 10 | function poolAdmins(address) external view returns (bool); 11 | 12 | function deposit(uint256) external; 13 | 14 | function increaseCustodyAllowance(address, uint256) external; 15 | 16 | function transferByCustodian(address, address, uint256) external; 17 | 18 | function poolState() external view returns (uint256); 19 | 20 | function deactivate() external; 21 | 22 | function finalize() external; 23 | 24 | function claim(address, address) external returns (uint256[7] memory); 25 | 26 | function setLockupPeriod(uint256) external; 27 | 28 | function setStakingFee(uint256) external; 29 | 30 | function setPoolAdmin(address, bool) external; 31 | 32 | function fundLoan(address, address, uint256) external; 33 | 34 | function withdraw(uint256) external; 35 | 36 | function superFactory() external view returns (address); 37 | 38 | function triggerDefault(address, address) external; 39 | 40 | function isPoolFinalized() external view returns (bool); 41 | 42 | function setOpenToPublic(bool) external; 43 | 44 | function setAllowList(address, bool) external; 45 | 46 | function allowedLiquidityProviders(address) external view returns (bool); 47 | 48 | function openToPublic() external view returns (bool); 49 | 50 | function intendToWithdraw() external; 51 | 52 | function DL_FACTORY() external view returns (uint8); 53 | 54 | function liquidityAsset() external view returns (address); 55 | 56 | function liquidityLocker() external view returns (address); 57 | 58 | function stakeAsset() external view returns (address); 59 | 60 | function stakeLocker() external view returns (address); 61 | 62 | function stakingFee() external view returns (uint256); 63 | 64 | function delegateFee() external view returns (uint256); 65 | 66 | function principalOut() external view returns (uint256); 67 | 68 | function liquidityCap() external view returns (uint256); 69 | 70 | function lockupPeriod() external view returns (uint256); 71 | 72 | function depositDate(address) external view returns (uint256); 73 | 74 | function debtLockers(address, address) external view returns (address); 75 | 76 | function withdrawCooldown(address) external view returns (uint256); 77 | 78 | function setLiquidityCap(uint256) external; 79 | 80 | function cancelWithdraw() external; 81 | 82 | function reclaimERC20(address) external; 83 | 84 | function BPTVal(address, address, address, address) external view returns (uint256); 85 | 86 | function isDepositAllowed(uint256) external view returns (bool); 87 | 88 | function getInitialStakeRequirements() external view returns (uint256, uint256, bool, uint256, uint256); 89 | 90 | } 91 | -------------------------------------------------------------------------------- /contracts/interfaces/IPoolFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IPoolFactory { 5 | 6 | function LL_FACTORY() external view returns (uint8); 7 | 8 | function SL_FACTORY() external view returns (uint8); 9 | 10 | function poolsCreated() external view returns (uint256); 11 | 12 | function globals() external view returns (address); 13 | 14 | function pools(uint256) external view returns (address); 15 | 16 | function isPool(address) external view returns (bool); 17 | 18 | function poolFactoryAdmins(address) external view returns (bool); 19 | 20 | function setGlobals(address) external; 21 | 22 | function createPool(address, address, address, address, uint256, uint256, uint256) external returns (address); 23 | 24 | function setPoolFactoryAdmin(address, bool) external; 25 | 26 | function pause() external; 27 | 28 | function unpause() external; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /contracts/interfaces/IPremiumCalc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IPremiumCalc { 5 | 6 | function calcType() external view returns (uint8); 7 | 8 | function name() external view returns (bytes32); 9 | 10 | function premiumFee() external view returns (uint256); 11 | 12 | function getPremiumPayment(address) external view returns (uint256, uint256, uint256); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IRepaymentCalc.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IRepaymentCalc { 5 | 6 | function calcType() external view returns (uint8); 7 | 8 | function name() external view returns (bytes32); 9 | 10 | function getNextPayment(address) external view returns (uint256, uint256, uint256); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IStakeLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "../token/interfaces/IStakeLockerFDT.sol"; 5 | 6 | interface IStakeLocker is IStakeLockerFDT { 7 | 8 | function stakeDate(address) external returns (uint256); 9 | 10 | function stake(uint256) external; 11 | 12 | function unstake(uint256) external; 13 | 14 | function pull(address, uint256) external; 15 | 16 | function setAllowlist(address, bool) external; 17 | 18 | function openStakeLockerToPublic() external; 19 | 20 | function openToPublic() external view returns (bool); 21 | 22 | function allowed(address) external view returns (bool); 23 | 24 | function updateLosses(uint256) external; 25 | 26 | function intendToUnstake() external; 27 | 28 | function unstakeCooldown(address) external view returns (uint256); 29 | 30 | function lockupPeriod() external view returns (uint256); 31 | 32 | function stakeAsset() external view returns (address); 33 | 34 | function liquidityAsset() external view returns (address); 35 | 36 | function pool() external view returns (address); 37 | 38 | function setLockupPeriod(uint256) external; 39 | 40 | function cancelUnstake() external; 41 | 42 | function increaseCustodyAllowance(address, uint256) external; 43 | 44 | function transferByCustodian(address, address, uint256) external; 45 | 46 | function pause() external; 47 | 48 | function unpause() external; 49 | 50 | function isUnstakeAllowed(address) external view returns (bool); 51 | 52 | function isReceiveAllowed(uint256) external view returns (bool); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /contracts/interfaces/IStakeLockerFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IStakeLockerFactory { 5 | 6 | function owner(address) external returns (address); 7 | 8 | function isLocker(address) external returns (bool); 9 | 10 | function factoryType() external returns (uint8); 11 | 12 | function newLocker(address, address) external returns (address); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/ISubFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface ISubFactory { 5 | 6 | function factoryType() external view returns (uint8); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IUniswapRouter { 5 | 6 | function swapExactTokensForTokens( 7 | uint256 amountIn, 8 | uint256 amountOutMin, 9 | address[] calldata path, 10 | address to, 11 | uint256 deadline 12 | ) external returns (uint256[] memory amounts); 13 | 14 | function swapETHForExactTokens( 15 | uint256 amountOut, 16 | address[] calldata path, 17 | address to, 18 | uint256 deadline 19 | ) external payable returns (uint256[] memory amounts); 20 | 21 | function quote( 22 | uint256 amountA, 23 | uint256 reserveA, 24 | uint256 reserveB 25 | ) external pure returns (uint256 amountB); 26 | 27 | function WETH() external pure returns (address); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /contracts/library/Util.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "../interfaces/IERC20Details.sol"; 5 | import "../interfaces/IMapleGlobals.sol"; 6 | import "lib/openzeppelin-contracts/contracts/math/SafeMath.sol"; 7 | 8 | /// @title Util is a library that contains utility functions. 9 | library Util { 10 | 11 | using SafeMath for uint256; 12 | 13 | /** 14 | @dev Calculates the minimum amount from a swap (adjustable for price slippage). 15 | @param globals Instance of a MapleGlobals. 16 | @param fromAsset Address of ERC-20 that will be swapped. 17 | @param toAsset Address of ERC-20 that will returned from swap. 18 | @param swapAmt Amount of `fromAsset` to be swapped. 19 | @return Expected amount of `toAsset` to receive from swap based on current oracle prices. 20 | */ 21 | function calcMinAmount(IMapleGlobals globals, address fromAsset, address toAsset, uint256 swapAmt) external view returns (uint256) { 22 | return 23 | swapAmt 24 | .mul(globals.getLatestPrice(fromAsset)) // Convert from `fromAsset` value. 25 | .mul(10 ** IERC20Details(toAsset).decimals()) // Convert to `toAsset` decimal precision. 26 | .div(globals.getLatestPrice(toAsset)) // Convert to `toAsset` value. 27 | .div(10 ** IERC20Details(fromAsset).decimals()); // Convert from `fromAsset` decimal precision. 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/math/SafeMathInt.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | library SafeMathInt { 5 | function toUint256Safe(int256 a) internal pure returns (uint256) { 6 | require(a >= 0, "SMI:NEG"); 7 | return uint256(a); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/math/SafeMathUint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | library SafeMathUint { 5 | function toInt256Safe(uint256 a) internal pure returns (int256 b) { 6 | b = int256(a); 7 | require(b >= 0, "SMU:OOB"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/oracles/ChainlinkOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./IChainlinkAggregatorV3.sol"; 5 | import "../interfaces/IMapleGlobals.sol"; 6 | import "lib/openzeppelin-contracts/contracts/access/Ownable.sol"; 7 | 8 | /// @title ChainlinkOracle is a wrapper contract for Chainlink oracle price feeds that allows for manual price feed overrides. 9 | contract ChainlinkOracle is Ownable { 10 | 11 | IChainlinkAggregatorV3 public priceFeed; 12 | IMapleGlobals public globals; 13 | 14 | address public immutable assetAddress; 15 | 16 | bool public manualOverride; 17 | int256 public manualPrice; 18 | 19 | event ChangeAggregatorFeed(address _newMedianizer, address _oldMedianizer); 20 | event SetManualPrice(int256 _oldPrice, int256 _newPrice); 21 | event SetManualOverride(bool _override); 22 | 23 | /** 24 | @dev Creates a new Chainlink based oracle. 25 | @param _aggregator Address of Chainlink aggregator. 26 | @param _assetAddress Address of currency (0x0 for ETH). 27 | @param _owner Address of the owner of the contract. 28 | */ 29 | constructor(address _aggregator, address _assetAddress, address _owner) public { 30 | require(_aggregator != address(0), "CO:ZERO_AGGREGATOR_ADDR"); 31 | priceFeed = IChainlinkAggregatorV3(_aggregator); 32 | assetAddress = _assetAddress; 33 | transferOwnership(_owner); 34 | } 35 | 36 | /** 37 | @dev Returns the latest price. 38 | @return price The latest price. 39 | */ 40 | function getLatestPrice() public view returns (int256) { 41 | if (manualOverride) return manualPrice; 42 | (uint80 roundID, int256 price,,uint256 timeStamp, uint80 answeredInRound) = priceFeed.latestRoundData(); 43 | 44 | require(timeStamp != 0, "CO:ROUND_NOT_COMPLETE"); 45 | require(answeredInRound >= roundID, "CO:STALE_DATA"); 46 | require(price != int256(0), "CO:ZERO_PRICE"); 47 | return price; 48 | } 49 | 50 | 51 | /** 52 | @dev Updates aggregator address. Only the contract Owner can call this function. 53 | @dev It emits a `ChangeAggregatorFeed` event. 54 | @param aggregator Address of Chainlink aggregator. 55 | */ 56 | function changeAggregator(address aggregator) external onlyOwner { 57 | require(aggregator != address(0), "CO:ZERO_AGGREGATOR_ADDR"); 58 | emit ChangeAggregatorFeed(aggregator, address(priceFeed)); 59 | priceFeed = IChainlinkAggregatorV3(aggregator); 60 | } 61 | 62 | /** 63 | @dev Returns address of oracle currency (0x0 for ETH). 64 | */ 65 | function getAssetAddress() external view returns (address) { 66 | return assetAddress; 67 | } 68 | 69 | /** 70 | @dev Returns denomination of price. 71 | */ 72 | function getDenomination() external pure returns (bytes32) { 73 | // All Chainlink oracles are denominated in USD. 74 | return bytes32("USD"); 75 | } 76 | 77 | /** 78 | @dev Sets a manual price. Only the contract Owner can call this function. 79 | NOTE: this can only be used if manualOverride == true. 80 | @dev It emits a `SetManualPrice` event. 81 | @param _price Price to set. 82 | */ 83 | function setManualPrice(int256 _price) public onlyOwner { 84 | require(manualOverride, "CO:MANUAL_OVERRIDE_NOT_ACTIVE"); 85 | emit SetManualPrice(manualPrice, _price); 86 | manualPrice = _price; 87 | } 88 | 89 | /** 90 | @dev Sets manual override, allowing for manual price setting. Only the contract Owner can call this function. 91 | @dev It emits a `SetManualOverride` event. 92 | @param _override Whether to use the manual override price or not. 93 | */ 94 | function setManualOverride(bool _override) public onlyOwner { 95 | manualOverride = _override; 96 | emit SetManualOverride(_override); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /contracts/oracles/IChainlinkAggregatorV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IChainlinkAggregatorV3 { 5 | 6 | function decimals() external view returns (uint8); 7 | function description() external view returns (string memory); 8 | function version() external view returns (uint256); 9 | 10 | // getRoundData and latestRoundData should both raise "No data present" 11 | // if they do not have data to report, instead of returning unset values, 12 | // which could be misinterpreted as actual reported values. 13 | 14 | function getRoundData(uint80 _roundId) 15 | external 16 | view 17 | returns ( 18 | uint80 roundId, 19 | int256 answer, 20 | uint256 startedAt, 21 | uint256 updatedAt, 22 | uint80 answeredInRound 23 | ); 24 | 25 | function latestRoundData() 26 | external 27 | view 28 | returns ( 29 | uint80 roundId, 30 | int256 answer, 31 | uint256 startedAt, 32 | uint256 updatedAt, 33 | uint80 answeredInRound 34 | ); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /contracts/oracles/UsdOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | /// @title UsdOracle is a constant price oracle feed that always returns 1 USD in 8 decimal precision. 5 | contract UsdOracle { 6 | 7 | int256 constant USD_PRICE = 1 * 10 ** 8; 8 | 9 | /** 10 | @dev Returns the constant USD price. 11 | */ 12 | function getLatestPrice() public pure returns (int256) { 13 | return USD_PRICE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/test/ChainLinkOracle.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract ChainlinkOracleTest is TestUtil { 8 | 9 | function setUp() public { 10 | setUpGlobals(); 11 | createCommoner(); 12 | setUpTokens(); 13 | setUpOracles(); 14 | } 15 | 16 | function test_getLatestPrice() public { 17 | // Assert initial state 18 | assertTrue( wethOracle.getLatestPrice() > int256(1)); 19 | assertTrue(!wethOracle.manualOverride()); 20 | assertEq( wethOracle.manualPrice(), int256(0)); 21 | 22 | // Try to set manual price before setting the manual override. 23 | assertTrue(!securityAdmin.try_setManualPrice(address(wethOracle), int256(45000))); 24 | 25 | // Enable oracle manual override 26 | assertTrue( !cam.try_setManualOverride(address(wethOracle), true)); 27 | assertTrue(securityAdmin.try_setManualOverride(address(wethOracle), true)); 28 | assertTrue( wethOracle.manualOverride()); 29 | 30 | // Set price manually 31 | assertTrue( !cam.try_setManualPrice(address(wethOracle), int256(45000))); 32 | assertTrue(securityAdmin.try_setManualPrice(address(wethOracle), int256(45000))); 33 | assertEq( wethOracle.manualPrice(), int256(45000)); 34 | assertEq( wethOracle.getLatestPrice(), int256(45000)); 35 | 36 | // Change aggregator 37 | assertTrue( !cam.try_changeAggregator(address(wethOracle), 0xb022E2970b3501d8d83eD07912330d178543C1eB)); 38 | assertTrue(securityAdmin.try_changeAggregator(address(wethOracle), 0xb022E2970b3501d8d83eD07912330d178543C1eB)); 39 | assertEq(address(wethOracle.priceFeed()), 0xb022E2970b3501d8d83eD07912330d178543C1eB); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/test/CollateralLockerFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract CollateralLockerFactoryTest is TestUtil { 8 | 9 | function setUp() public { 10 | setUpGlobals(); 11 | setUpTokens(); 12 | createCollateralLockerFactory(); 13 | createBorrower(); 14 | } 15 | 16 | function test_newLocker() public { 17 | CollateralLocker cl = CollateralLocker(clFactory.newLocker(USDC)); 18 | 19 | // Validate the storage of clfactory. 20 | assertEq(clFactory.owner(address(cl)), address(this), "Invalid owner"); 21 | assertTrue(clFactory.isLocker(address(cl))); 22 | 23 | // Validate the storage of cl. 24 | assertEq(cl.loan(), address(this), "Incorrect loan address"); 25 | assertEq(address(cl.collateralAsset()), USDC, "Incorrect address of collateral asset"); 26 | 27 | // Assert that no one can access CollateralLocker funds 28 | mint("USDC", address(cl), 500 * USD); 29 | assertTrue(!bob.try_pull(address(cl), address(bob), 10)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/test/DebtLockerFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract DebtLockerFactoryTest is TestUtil { 8 | 9 | function setUp() public { 10 | setUpGlobals(); 11 | setUpTokens(); 12 | createBorrower(); 13 | setUpFactories(); 14 | setUpCalcs(); 15 | createLoan(); 16 | } 17 | 18 | function test_newLocker() public { 19 | DebtLocker dl = DebtLocker(dlFactory.newLocker(address(loan))); 20 | // Validate the storage of dlfactory. 21 | assertEq( dlFactory.owner(address(dl)), address(this)); 22 | assertTrue(dlFactory.isLocker(address(dl))); 23 | 24 | // Validate the storage of dl. 25 | assertEq(address(dl.loan()), address(loan), "Incorrect loan address"); 26 | assertEq(dl.pool(), address(this), "Incorrect owner of the DebtLocker"); 27 | assertEq(address(dl.liquidityAsset()), USDC, "Incorrect address of loan asset"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/test/FundingLockerFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract FundingLockerFactoryTest is TestUtil { 8 | 9 | function setUp() public { 10 | setUpGlobals(); 11 | createFundingLockerFactory(); 12 | } 13 | 14 | function test_newLocker() public { 15 | FundingLocker fl = FundingLocker(flFactory.newLocker(USDC)); 16 | 17 | // Validate the storage of flfactory. 18 | assertEq(flFactory.owner(address(fl)), address(this)); 19 | assertTrue(flFactory.isLocker(address(fl))); 20 | 21 | // Validate the storage of fl. 22 | assertEq(fl.loan(), address(this), "Incorrect loan address"); 23 | assertEq(address(fl.liquidityAsset()), USDC, "Incorrect address of loan asset"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/Gulp.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract GulpTest is TestUtil { 8 | 9 | using SafeMath for uint256; 10 | 11 | function setUp() public { 12 | setUpGlobals(); 13 | setUpTokens(); 14 | setUpOracles(); 15 | setUpFactories(); 16 | setUpCalcs(); 17 | setUpActors(); 18 | setUpBalancerPoolForStakers(); 19 | setUpLiquidityPool(); 20 | createLoan(); 21 | } 22 | 23 | function setUpLoanAndDrawdown() public { 24 | mint("USDC", address(leo), 10_000_000 * USD); // Mint USDC to LP 25 | leo.approve(USDC, address(pool), MAX_UINT); // LP approves USDC 26 | 27 | leo.deposit(address(pool), 10_000_000 * USD); // LP deposits 10m USDC to Pool 28 | pat.fundLoan(address(pool), address(loan), address(dlFactory), 10_000_000 * USD); // PD funds loan for 10m USDC 29 | 30 | uint256 cReq = loan.collateralRequiredForDrawdown(10_000_000 * USD); // WETH required for 100_000_000 USDC drawdown on loan 31 | mint("WETH", address(bob), cReq); // Mint WETH to borrower 32 | bob.approve(WETH, address(loan), MAX_UINT); // Borrower approves WETH 33 | bob.drawdown(address(loan), 10_000_000 * USD); // Borrower draws down 10m USDC 34 | } 35 | 36 | function test_gulp() public { 37 | 38 | Governor fakeGov = new Governor(); 39 | fakeGov.setGovGlobals(globals); // Point to globals created by gov 40 | 41 | gov.setGovTreasury(treasury); 42 | fakeGov.setGovTreasury(treasury); 43 | 44 | // Drawdown on loan will transfer fee to MPL token contract. 45 | setUpLoanAndDrawdown(); 46 | 47 | // Treasury processes fees, sends to MPL token holders. 48 | // treasury.distributeToHolders(); 49 | assertTrue(!fakeGov.try_distributeToHolders()); 50 | assertTrue( gov.try_distributeToHolders()); 51 | 52 | uint256 totalFundsToken = usdc.balanceOf(address(mpl)); 53 | uint256 mplBal = mpl.balanceOf(address(bPool)); 54 | uint256 earnings = mpl.withdrawableFundsOf(address(bPool)); 55 | 56 | assertEq(totalFundsToken, loan.principalOwed() * globals.treasuryFee() / 10_000); 57 | assertEq(mplBal, 155_000 * WAD); 58 | withinDiff(earnings, totalFundsToken * mplBal / mpl.totalSupply(), 1); 59 | 60 | // MPL is held by Balancer Pool, claim on behalf of BPool. 61 | mpl.withdrawFundsOnBehalf(address(bPool)); 62 | 63 | uint256 usdcBal_preGulp = bPool.getBalance(USDC); 64 | 65 | bPool.gulp(USDC); // Update BPool with gulp(token). 66 | 67 | uint256 usdcBal_postGulp = bPool.getBalance(USDC); 68 | 69 | assertEq(usdcBal_preGulp, 1_550_000 * USD); 70 | assertEq(usdcBal_postGulp, usdcBal_preGulp + earnings); // USDC is transferred into Balancer pool, increasing value of MPL 71 | } 72 | 73 | function test_uniswap_pool_skim() public { 74 | setUpUniswapMplUsdcPool(75_000 * WAD, 1_500_000 * USD); 75 | 76 | gov.setGovTreasury(treasury); 77 | // Drawdown on loan will transfer fee to MPL token contract. 78 | setUpLoanAndDrawdown(); 79 | 80 | assertTrue(gov.try_distributeToHolders()); 81 | 82 | uint256 totalFundsToken = IERC20(USDC).balanceOf(address(mpl)); 83 | uint256 mplBal = mpl.balanceOf(address(uniswapPair)); 84 | uint256 earnings = mpl.withdrawableFundsOf(address(uniswapPair)); 85 | 86 | assertEq(totalFundsToken, loan.principalOwed() * globals.treasuryFee() / 10_000); 87 | assertEq(mplBal, 75_000 * WAD); 88 | withinDiff(earnings, totalFundsToken * mplBal / mpl.totalSupply(), 1); 89 | 90 | (uint256 before_reserve0, uint256 before_reserve1, ) = uniswapPair.getReserves(); 91 | 92 | // MPL is held by Balancer Pool, claim on behalf of BPool. 93 | mpl.withdrawFundsOnBehalf(address(uniswapPair)); 94 | 95 | (uint256 after_reserve0, uint256 after_reserve1, ) = uniswapPair.getReserves(); 96 | 97 | assertEq(before_reserve0, after_reserve0, "Should not be any change in reserve0"); 98 | assertEq(before_reserve1, after_reserve1, "Should not be any change in reserve1"); 99 | 100 | uint256 usdcBal_preSkim = usdc.balanceOf(address(uniswapPair)); 101 | uint256 lex_usdcBal_preSkim = usdc.balanceOf(address(lex)); 102 | 103 | uniswapPair.skim(address(lex)); // Get the extra fund out of it. 104 | 105 | uint256 usdcBal_postSkim = usdc.balanceOf(address(uniswapPair)); 106 | uint256 lex_usdcBal_postSkim = usdc.balanceOf(address(lex)); 107 | 108 | (uint256 reserve0_afterSkim, uint256 reserve1_afterSkim, ) = uniswapPair.getReserves(); 109 | 110 | assertEq(usdcBal_preSkim - usdcBal_postSkim, earnings, "Should only transfer earnings amount"); 111 | assertEq(lex_usdcBal_postSkim - lex_usdcBal_preSkim, earnings, "Should lex's USDC balance increase by earnings"); 112 | assertEq(reserve0_afterSkim, after_reserve0, "Should not be a change"); 113 | assertEq(reserve1_afterSkim, after_reserve1, "Should not be a change"); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /contracts/test/LoanLiquidation.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract LoanLiquidationTest is TestUtil { 8 | 9 | function setUp() public { 10 | 11 | setUpGlobals(); 12 | setUpTokens(); 13 | setUpOracles(); 14 | setUpFactories(); 15 | setUpCalcs(); 16 | setUpActors(); 17 | setUpBalancerPool(); 18 | setUpLiquidityPool(); 19 | createLoans(); 20 | 21 | /*** Mint balances to relevant actors ***/ 22 | mint("WETH", address(bob), 100 ether); 23 | mint("WBTC", address(bob), 10 * BTC); 24 | mint("USDC", address(leo), 100_000 * USD); 25 | mint("USDC", address(bob), 100_000 * USD); 26 | mint("USDC", address(this), 50_000_000 * USD); 27 | 28 | /*** LP deposits USDC into Pool ***/ 29 | leo.approve(USDC, address(pool), MAX_UINT); 30 | leo.deposit(address(pool), 5000 * USD); 31 | } 32 | 33 | function createAndFundLoan(address _interestStructure, address _collateral, uint256 collateralRatio) internal returns (Loan loan) { 34 | uint256[5] memory specs = [500, 90, 30, uint256(1000 * USD), collateralRatio]; 35 | address[3] memory calcs = [_interestStructure, address(lateFeeCalc), address(premiumCalc)]; 36 | 37 | loan = bob.createLoan(address(loanFactory), USDC, _collateral, address(flFactory), address(clFactory), specs, calcs); 38 | 39 | pat.fundLoan(address(pool), address(loan), address(dlFactory), 1000 * USD); 40 | 41 | bob.approve(_collateral, address(loan), MAX_UINT); 42 | assertTrue(bob.try_drawdown(address(loan), 1000 * USD)); // Borrow draws down 1000 USDC 43 | } 44 | 45 | function performLiquidationAssertions(Loan loan) internal { 46 | // Fetch pre-state variables. 47 | address collateralLocker = loan.collateralLocker(); 48 | address collateralAsset = address(loan.collateralAsset()); 49 | uint256 collateralBalance = IERC20(collateralAsset).balanceOf(address(collateralLocker)); 50 | 51 | uint256 principalOwed_pre = loan.principalOwed(); 52 | uint256 liquidityAssetLoan_pre = IERC20(USDC).balanceOf(address(loan)); 53 | uint256 liquidityAssetBorr_pre = IERC20(USDC).balanceOf(address(bob)); 54 | 55 | // Warp to late payment. 56 | hevm.warp(block.timestamp + loan.nextPaymentDue() + globals.defaultGracePeriod() + 1); 57 | 58 | // Pre-state triggerDefault() checks. 59 | assertEq(uint256(loan.loanState()), 1); 60 | assertEq(IERC20(collateralAsset).balanceOf(address(collateralLocker)), collateralBalance); 61 | 62 | pat.triggerDefault(address(pool), address(loan), address(dlFactory)); 63 | 64 | { 65 | uint256 principalOwed_post = loan.principalOwed(); 66 | uint256 liquidityAssetLoan_post = IERC20(USDC).balanceOf(address(loan)); 67 | uint256 liquidityAssetBorr_post = IERC20(USDC).balanceOf(address(bob)); 68 | uint256 amountLiquidated = loan.amountLiquidated(); 69 | uint256 amountRecovered = loan.amountRecovered(); 70 | uint256 defaultSuffered = loan.defaultSuffered(); 71 | uint256 liquidationExcess = loan.liquidationExcess(); 72 | 73 | // Post-state triggerDefault() checks. 74 | assertEq(uint256(loan.loanState()), 4); 75 | assertEq(IERC20(collateralAsset).balanceOf(address(collateralLocker)), 0); 76 | assertEq(amountLiquidated, collateralBalance); 77 | 78 | if (amountRecovered > principalOwed_pre) { 79 | assertEq(liquidityAssetBorr_post - liquidityAssetBorr_pre, liquidationExcess); 80 | assertEq(principalOwed_post, 0); 81 | assertEq(liquidationExcess, amountRecovered - principalOwed_pre); 82 | assertEq(defaultSuffered, 0); 83 | assertEq( 84 | amountRecovered, 85 | (liquidityAssetBorr_post - liquidityAssetBorr_pre) + (liquidityAssetLoan_post - liquidityAssetLoan_pre) 86 | ); 87 | } 88 | else { 89 | assertEq(principalOwed_post, principalOwed_pre - amountRecovered); 90 | assertEq(defaultSuffered, principalOwed_post); 91 | assertEq(liquidationExcess, 0); 92 | assertEq(amountRecovered, liquidityAssetLoan_post - liquidityAssetLoan_pre); 93 | } 94 | } 95 | } 96 | 97 | function test_basic_liquidation() public { 98 | // Triangular uniswap path 99 | Loan wbtcLoan = createAndFundLoan(address(repaymentCalc), WBTC, 2000); 100 | performLiquidationAssertions(wbtcLoan); 101 | 102 | // Bilateral uniswap path 103 | Loan wethLoan = createAndFundLoan(address(repaymentCalc), WETH, 2000); 104 | performLiquidationAssertions(wethLoan); 105 | 106 | // collateralAsset == liquidityAsset 107 | Loan usdcLoan = createAndFundLoan(address(repaymentCalc), USDC, 2000); 108 | performLiquidationAssertions(usdcLoan); 109 | 110 | // Zero collateralization 111 | Loan wethLoan2 = createAndFundLoan(address(repaymentCalc), WETH, 0); 112 | performLiquidationAssertions(wethLoan2); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /contracts/test/MapleTreasury.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract MapleTreasuryTest is TestUtil { 8 | 9 | function setUp() public { 10 | setUpGlobals(); 11 | setUpTokens(); 12 | setUpOracles(); 13 | createHolders(); 14 | 15 | mint("WBTC", address(this), 10 * BTC); 16 | mint("WETH", address(this), 10 ether); 17 | mint("DAI", address(this), 100 ether); 18 | mint("USDC", address(this), 100 * USD); 19 | } 20 | 21 | function test_setGlobals() public { 22 | MapleGlobals globals2 = fakeGov.createGlobals(address(mpl)); // Create upgraded MapleGlobals 23 | 24 | assertEq(address(treasury.globals()), address(globals)); 25 | 26 | assertTrue(!fakeGov.try_setGlobals(address(treasury), address(globals2))); // Non-governor cannot set new globals 27 | 28 | globals2 = gov.createGlobals(address(mpl)); // Create upgraded MapleGlobals 29 | 30 | assertTrue(gov.try_setGlobals(address(treasury), address(globals2))); // Governor can set new globals 31 | assertEq(address(treasury.globals()), address(globals2)); // Globals is updated 32 | } 33 | 34 | function test_withdrawFunds() public { 35 | assertEq(IERC20(USDC).balanceOf(address(treasury)), 0); 36 | 37 | IERC20(USDC).transfer(address(treasury), 100 * USD); 38 | 39 | assertEq(IERC20(USDC).balanceOf(address(treasury)), 100 * USD); 40 | assertEq(IERC20(USDC).balanceOf(address(gov)), 0); 41 | 42 | assertTrue(!fakeGov.try_reclaimERC20_treasury(USDC, 40 * USD)); // Non-governor can't withdraw 43 | assertTrue( gov.try_reclaimERC20_treasury(USDC, 40 * USD)); 44 | 45 | assertEq(IERC20(USDC).balanceOf(address(treasury)), 60 * USD); // Can be distributed to MPL holders 46 | assertEq(IERC20(USDC).balanceOf(address(gov)), 40 * USD); // Withdrawn to MapleDAO address for funding 47 | } 48 | 49 | function test_distributeToHolders() public { 50 | assertEq(mpl.balanceOf(address(hal)), 0); 51 | assertEq(mpl.balanceOf(address(hue)), 0); 52 | 53 | mpl.transfer(address(hal), mpl.totalSupply() * 25 / 100); // 25% 54 | mpl.transfer(address(hue), mpl.totalSupply() * 75 / 100); // 75% 55 | 56 | assertEq(mpl.balanceOf(address(hal)), 2_500_000 ether); 57 | assertEq(mpl.balanceOf(address(hue)), 7_500_000 ether); 58 | 59 | assertEq(IERC20(USDC).balanceOf(address(treasury)), 0); 60 | 61 | IERC20(USDC).transfer(address(treasury), 100 * USD); 62 | 63 | assertEq(IERC20(USDC).balanceOf(address(treasury)), 100 * USD); 64 | assertEq(IERC20(USDC).balanceOf(address(mpl)), 0); 65 | 66 | assertTrue(!fakeGov.try_distributeToHolders()); // Non-governor can't distribute 67 | assertTrue( gov.try_distributeToHolders()); // Governor can distribute 68 | 69 | assertEq(IERC20(USDC).balanceOf(address(treasury)), 0); // Withdraws all funds 70 | assertEq(IERC20(USDC).balanceOf(address(mpl)), 100 * USD); // Withdrawn to MPL address, where accounts can claim funds 71 | 72 | assertEq(IERC20(USDC).balanceOf(address(hal)), 0); // Token holder hasn't claimed 73 | assertEq(IERC20(USDC).balanceOf(address(hue)), 0); // Token holder hasn't claimed 74 | 75 | hal.withdrawFunds(address(mpl)); 76 | hue.withdrawFunds(address(mpl)); 77 | 78 | withinDiff(IERC20(USDC).balanceOf(address(hal)), 25 * USD, 1); // Token holder has claimed proportional share of USDC 79 | withinDiff(IERC20(USDC).balanceOf(address(hue)), 75 * USD, 1); // Token holder has claimed proportional share of USDC 80 | } 81 | 82 | function test_convertERC20() public { 83 | 84 | IMapleGlobals _globals = IMapleGlobals(address(globals)); 85 | 86 | assertEq(IERC20(WBTC).balanceOf(address(treasury)), 0); 87 | assertEq(IERC20(WETH).balanceOf(address(treasury)), 0); 88 | assertEq(IERC20(DAI).balanceOf(address(treasury)), 0); 89 | 90 | IERC20(WBTC).transfer(address(treasury), 10 * BTC); 91 | IERC20(WETH).transfer(address(treasury), 10 ether); 92 | IERC20(DAI).transfer(address(treasury), 100 ether); 93 | 94 | assertEq(IERC20(WBTC).balanceOf(address(treasury)), 10 * BTC); 95 | assertEq(IERC20(WETH).balanceOf(address(treasury)), 10 ether); 96 | assertEq(IERC20(DAI).balanceOf(address(treasury)), 100 ether); 97 | assertEq(IERC20(USDC).balanceOf(address(treasury)), 0); 98 | 99 | uint256 expectedAmtFromWBTC = Util.calcMinAmount(_globals, WBTC, USDC, 10 * BTC); 100 | uint256 expectedAmtFromWETH = Util.calcMinAmount(_globals, WETH, USDC, 10 ether); 101 | uint256 expectedAmtFromDAI = Util.calcMinAmount(_globals, DAI, USDC, 100 ether); 102 | 103 | /*** Convert WBTC ***/ 104 | assertTrue(!fakeGov.try_convertERC20(WBTC)); // Non-governor can't convert 105 | assertTrue( gov.try_convertERC20(WBTC)); // Governor can convert 106 | 107 | assertEq(IERC20(WBTC).balanceOf(address(treasury)), 0); 108 | assertEq(IERC20(DAI).balanceOf(address(treasury)), 100 ether); 109 | 110 | withinPrecision(IERC20(USDC).balanceOf(address(treasury)), expectedAmtFromWBTC, 2); 111 | 112 | gov.distributeToHolders(); // Empty treasury balance of USDC 113 | 114 | /*** Convert WETH ***/ 115 | assertTrue(!fakeGov.try_convertERC20(WETH)); // Non-governor can't convert 116 | assertTrue( gov.try_convertERC20(WETH)); // Governor can convert 117 | 118 | assertEq(IERC20(WETH).balanceOf(address(treasury)), 0); 119 | assertEq(IERC20(DAI).balanceOf(address(treasury)), 100 ether); 120 | 121 | withinPrecision(IERC20(USDC).balanceOf(address(treasury)), expectedAmtFromWETH, 2); 122 | 123 | gov.distributeToHolders(); // Empty treasury balance of USDC 124 | 125 | /*** Convert DAI ***/ 126 | assertTrue(!fakeGov.try_convertERC20(DAI)); // Non-governor can't convert 127 | assertTrue( gov.try_convertERC20(DAI)); // Governor can convert 128 | 129 | assertEq(IERC20(WETH).balanceOf(address(treasury)), 0); 130 | assertEq(IERC20(DAI).balanceOf(address(treasury)), 0); 131 | 132 | withinPrecision(IERC20(USDC).balanceOf(address(treasury)), expectedAmtFromDAI, 2); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /contracts/test/MplRewardsFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract MplRewardsFactoryTest is TestUtil { 8 | 9 | function setUp() public { 10 | setUpGlobals(); 11 | setUpMplRewardsFactory(); 12 | } 13 | 14 | function test_constructor() public { 15 | MplRewardsFactory _mplRewardsFactory = new MplRewardsFactory(address(globals)); // Setup MplRewardsFactory to support MplRewards creation. 16 | assertEq(address(_mplRewardsFactory.globals()), address(globals)); 17 | } 18 | 19 | function test_createMplRewards() public { 20 | address mockPool = address(1); // Fake pool address so a pool doesn't have to be instantiated for PoolFDTs 21 | 22 | // Assert permissioning 23 | assertTrue(!fakeGov.try_createMplRewards(address(mpl), mockPool)); 24 | assertTrue( gov.try_createMplRewards(address(mpl), mockPool)); 25 | 26 | MplRewards mplRewards = MplRewards(gov.createMplRewards(address(mpl), mockPool)); 27 | 28 | // Validate the storage of mplRewardsFactory 29 | assertTrue(mplRewardsFactory.isMplRewards(address(mplRewards))); 30 | 31 | // Validate the storage of mplRewards. 32 | assertEq(address(mplRewards.rewardsToken()), address(mpl)); 33 | assertEq(address(mplRewards.stakingToken()), mockPool); 34 | assertEq(mplRewards.rewardsDuration(), 7 days); 35 | assertEq(address(mplRewards.owner()), address(gov)); 36 | } 37 | 38 | function test_setGlobals() public { 39 | assertTrue(!fakeGov.try_setGlobals(address(mplRewardsFactory), address(1))); 40 | assertTrue( gov.try_setGlobals(address(mplRewardsFactory), address(1))); 41 | assertEq(address(mplRewardsFactory.globals()), address(1)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/test/PoolExcess.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract PoolExcessTest is TestUtil { 8 | 9 | using SafeMath for uint256; 10 | 11 | function setUp() public { 12 | setUpGlobals(); 13 | setUpTokens(); 14 | setUpOracles(); 15 | setUpFactories(); 16 | setUpCalcs(); 17 | setUpActors(); 18 | setUpBalancerPoolForPools(); 19 | setUpLiquidityPools(); 20 | createLoan(); 21 | } 22 | 23 | function setUpLoan() public { 24 | // Fund the pool 25 | mint("USDC", address(leo), 20_000_000 * USD); 26 | leo.approve(USDC, address(pool), MAX_UINT); 27 | leo.approve(USDC, address(pool2), MAX_UINT); 28 | leo.deposit(address(pool), 10_000_000 * USD); 29 | leo.deposit(address(pool2), 10_000_000 * USD); 30 | 31 | // Fund the loan 32 | pat.fundLoan(address(pool), address(loan), address(dlFactory), 1_000_000 * USD); 33 | pam.fundLoan(address(pool2), address(loan), address(dlFactory), 3_000_000 * USD); 34 | } 35 | 36 | function test_unwind_loan_reclaim() public { 37 | 38 | setUpLoan(); 39 | 40 | // Warp and call unwind() 41 | hevm.warp(loan.createdAt() + globals.fundingPeriod() + 1); 42 | assertTrue(bob.try_unwind(address(loan))); 43 | 44 | uint256 principalOut_a_pre = pool.principalOut(); 45 | uint256 principalOut_b_pre = pool2.principalOut(); 46 | uint256 llBalance_a_pre = IERC20(pool.liquidityAsset()).balanceOf(pool.liquidityLocker()); 47 | uint256 llBalance_b_pre = IERC20(pool2.liquidityAsset()).balanceOf(pool2.liquidityLocker()); 48 | 49 | // Claim unwind() excessReturned 50 | uint256[7] memory vals_a = pat.claim(address(pool), address(loan), address(dlFactory)); 51 | uint256[7] memory vals_b = pam.claim(address(pool2), address(loan), address(dlFactory)); 52 | 53 | uint256 principalOut_a_post = pool.principalOut(); 54 | uint256 principalOut_b_post = pool2.principalOut(); 55 | uint256 llBalance_a_post = IERC20(pool.liquidityAsset()).balanceOf(pool.liquidityLocker()); 56 | uint256 llBalance_b_post = IERC20(pool2.liquidityAsset()).balanceOf(pool2.liquidityLocker()); 57 | 58 | assertEq(principalOut_a_pre - principalOut_a_post, vals_a[4]); 59 | assertEq(principalOut_b_pre - principalOut_b_post, vals_b[4]); 60 | assertEq(llBalance_a_post - llBalance_a_pre, vals_a[4]); 61 | assertEq(llBalance_b_post - llBalance_b_pre, vals_b[4]); 62 | 63 | // pool invested 1mm USD 64 | // pool2 invested 3mm USD 65 | withinDiff(principalOut_a_pre - principalOut_a_post, 1_000_000 * USD, 1); 66 | withinDiff(principalOut_b_pre - principalOut_b_post, 3_000_000 * USD, 1); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /contracts/test/PoolFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract PoolFactoryTest is TestUtil { 8 | 9 | function setUp() public { 10 | setUpGlobals(); 11 | setUpPoolDelegate(); 12 | setUpTokens(); 13 | setUpFactories(); 14 | setUpOracles(); 15 | setUpBalancerPool(); 16 | } 17 | 18 | function test_setGlobals() public { 19 | MapleGlobals globals2 = fakeGov.createGlobals(address(mpl)); // Create upgraded MapleGlobals 20 | 21 | assertEq(address(poolFactory.globals()), address(globals)); 22 | 23 | assertTrue(!fakeGov.try_setGlobals(address(poolFactory), address(globals2))); // Non-governor cannot set new globals 24 | 25 | globals2 = gov.createGlobals(address(mpl)); // Create upgraded MapleGlobals 26 | 27 | assertTrue(gov.try_setGlobals(address(poolFactory), address(globals2))); // Governor can set new globals 28 | assertEq(address(poolFactory.globals()), address(globals2)); // Globals is updated 29 | } 30 | 31 | function createPoolFails() internal returns (bool) { 32 | return !pat.try_createPool( 33 | address(poolFactory), 34 | USDC, 35 | address(bPool), // Passing in address of pool delegate for StakeAsset, an EOA which should fail isBPool check. 36 | address(slFactory), 37 | address(llFactory), 38 | 500, 39 | 100, 40 | MAX_UINT 41 | ); 42 | } 43 | 44 | function test_createPool_globals_validations() public { 45 | 46 | gov.setValidPoolFactory(address(poolFactory), true); 47 | 48 | // PoolFactory:INVALID_LL_FACTORY 49 | gov.setValidSubFactory(address(poolFactory), address(llFactory), false); 50 | assertTrue(createPoolFails()); 51 | gov.setValidSubFactory(address(poolFactory), address(llFactory), true); 52 | 53 | // PoolFactory:INVALID_SL_FACTORY 54 | gov.setValidSubFactory(address(poolFactory), address(slFactory), false); 55 | assertTrue(createPoolFails()); 56 | gov.setValidSubFactory(address(poolFactory), address(slFactory), true); 57 | 58 | // PoolFactory:MSG_SENDER_NOT_ALLOWED 59 | gov.setPoolDelegateAllowlist(address(pat), false); 60 | assertTrue(createPoolFails()); 61 | gov.setPoolDelegateAllowlist(address(pat), true); 62 | 63 | // PoolFactory:LIQ_ASSET_NOT_ALLOWED 64 | gov.setLiquidityAsset(USDC, false); 65 | assertTrue(createPoolFails()); 66 | gov.setLiquidityAsset(USDC, true); 67 | } 68 | 69 | function test_createPool_bad_stakeAsset() public { 70 | 71 | // PoolFactory:STAKE_ASSET_NOT_BPOOL 72 | assertTrue(!pat.try_createPool( 73 | address(poolFactory), 74 | USDC, 75 | address(pat), // Passing in address of pool delegate for StakeAsset, an EOA which should fail isBPool check. 76 | address(slFactory), 77 | address(llFactory), 78 | 500, 79 | 100, 80 | MAX_UINT 81 | )); 82 | } 83 | 84 | function test_createPool_wrong_staking_pair_asset() public { 85 | 86 | gov.setLiquidityAsset(DAI, true); 87 | 88 | // Pool:Pool:INVALID_STAKING_POOL 89 | assertTrue(!pat.try_createPool( 90 | address(poolFactory), 91 | DAI, 92 | address(bPool), // This pool uses MPL/USDC, so it can't cover DAI losses 93 | address(slFactory), 94 | address(llFactory), 95 | 500, 96 | 100, 97 | MAX_UINT 98 | )); 99 | } 100 | 101 | // Tests failure mode in createStakeLocker 102 | function test_createPool_createStakeLocker_no_mpl_token() public { 103 | 104 | mint("USDC", address(this), 50_000_000 * 10 ** 6); 105 | mint("DAI", address(this), 50_000_000 ether); 106 | 107 | // Initialize USDC/USDC Balancer pool (Doesn't include mpl) 108 | bPool = IBPool(IBFactory(BPOOL_FACTORY).newBPool()); 109 | 110 | IERC20(DAI).approve(address(bPool), uint256(-1)); 111 | IERC20(USDC).approve(address(bPool), uint256(-1)); 112 | 113 | bPool.bind(USDC, 50_000_000 * 10 ** 6, 5 * WAD); // Bind 50m DAI with 5 denormalization weight 114 | bPool.bind(DAI, 50_000_000 * WAD, 5 * WAD); // Bind 100k USDC with 5 denormalization weight 115 | 116 | assertEq(IERC20(USDC).balanceOf(address(bPool)), 50_000_000 * 10 ** 6); 117 | assertEq(IERC20(DAI).balanceOf(address(bPool)), 50_000_000 * WAD); 118 | bPool.finalize(); 119 | 120 | assertTrue(!pat.try_createPool( 121 | address(poolFactory), 122 | USDC, 123 | address(bPool), 124 | address(slFactory), 125 | address(llFactory), 126 | 500, 127 | 100, 128 | MAX_UINT 129 | )); 130 | } 131 | 132 | function test_createPool_invalid_fees() public { 133 | 134 | // PoolLib:INVALID_FEES 135 | assertTrue(!pat.try_createPool( 136 | address(poolFactory), 137 | USDC, 138 | address(bPool), 139 | address(slFactory), 140 | address(llFactory), 141 | 5000, // 50.00% 142 | 5001, // 50.01% 143 | MAX_UINT 144 | )); 145 | 146 | assertTrue(pat.try_createPool( 147 | address(poolFactory), 148 | USDC, 149 | address(bPool), 150 | address(slFactory), 151 | address(llFactory), 152 | 5000, // 50.00% 153 | 5000, // 50.00% 154 | MAX_UINT 155 | )); 156 | } 157 | 158 | // Tests failure mode in createStakeLocker 159 | function test_createPool_createStakeLocker_bPool_not_finalized() public { 160 | 161 | bPool = IBPool(IBFactory(BPOOL_FACTORY).newBPool()); 162 | mint("USDC", address(this), 50_000_000 * USD); 163 | usdc.approve(address(bPool), MAX_UINT); 164 | mpl.approve(address(bPool), MAX_UINT); 165 | bPool.bind(USDC, 50_000_000 * USD, 5 ether); // Bind 50m USDC with 5 denormalization weight 166 | bPool.bind(address(mpl), 100_000 * WAD, 5 ether); // Bind 100k MPL with 5 denormalization weight 167 | 168 | // Pool:INVALID_BALANCER_POOL 169 | assertTrue(!pat.try_createPool( 170 | address(poolFactory), 171 | USDC, 172 | address(bPool), 173 | address(slFactory), 174 | address(llFactory), 175 | 500, 176 | 100, 177 | MAX_UINT 178 | )); 179 | } 180 | 181 | function test_createPool_paused() public { 182 | 183 | // Pause PoolFactory and attempt createPool() 184 | assertTrue( gov.try_pause(address(poolFactory))); 185 | assertTrue(!pat.try_createPool( 186 | address(poolFactory), 187 | USDC, 188 | address(bPool), 189 | address(slFactory), 190 | address(llFactory), 191 | 500, 192 | 100, 193 | MAX_UINT 194 | )); 195 | assertEq(poolFactory.poolsCreated(), 0); 196 | 197 | // Unpause PoolFactory and createPool() 198 | assertTrue(gov.try_unpause(address(poolFactory))); 199 | assertTrue(pat.try_createPool( 200 | address(poolFactory), 201 | USDC, 202 | address(bPool), 203 | address(slFactory), 204 | address(llFactory), 205 | 500, 206 | 100, 207 | MAX_UINT 208 | )); 209 | assertEq(poolFactory.poolsCreated(), 1); 210 | 211 | // Pause protocol and attempt createPool() 212 | assertTrue(!globals.protocolPaused()); 213 | assertTrue( emergencyAdmin.try_setProtocolPause(address(globals), true)); 214 | assertTrue(!pat.try_createPool( 215 | address(poolFactory), 216 | USDC, 217 | address(bPool), 218 | address(slFactory), 219 | address(llFactory), 220 | 500, 221 | 100, 222 | MAX_UINT 223 | )); 224 | assertEq(poolFactory.poolsCreated(), 1); 225 | 226 | // Unpause protocol and createPool() 227 | assertTrue(emergencyAdmin.try_setProtocolPause(address(globals), false)); 228 | assertTrue(pat.try_createPool( 229 | address(poolFactory), 230 | USDC, 231 | address(bPool), 232 | address(slFactory), 233 | address(llFactory), 234 | 500, 235 | 100, 236 | MAX_UINT 237 | )); 238 | assertEq(poolFactory.poolsCreated(), 2); 239 | } 240 | 241 | function test_createPool_overflow() public { 242 | 243 | assertTrue(!pat.try_createPool( 244 | address(poolFactory), 245 | USDC, 246 | address(bPool), 247 | address(slFactory), 248 | address(llFactory), 249 | 500, 250 | MAX_UINT, 251 | MAX_UINT 252 | )); 253 | assertEq(poolFactory.poolsCreated(), 0); 254 | } 255 | 256 | function test_createPool() public { 257 | 258 | assertTrue(pat.try_createPool( 259 | address(poolFactory), 260 | USDC, 261 | address(bPool), 262 | address(slFactory), 263 | address(llFactory), 264 | 500, 265 | 100, 266 | MAX_UINT 267 | )); 268 | 269 | Pool pool = Pool(poolFactory.pools(0)); 270 | 271 | assertTrue(address(pool) != address(0)); 272 | assertTrue(poolFactory.isPool(address(pool))); 273 | assertEq(poolFactory.poolsCreated(), 1); 274 | 275 | assertEq(address(pool.liquidityAsset()), USDC); 276 | assertEq(pool.stakeAsset(), address(bPool)); 277 | assertEq(pool.poolDelegate(), address(pat)); 278 | assertEq(pool.stakingFee(), 500); 279 | assertEq(pool.delegateFee(), 100); 280 | assertEq(pool.liquidityCap(), MAX_UINT); 281 | 282 | assertTrue(pool.stakeLocker() != address(0)); 283 | assertTrue(pool.liquidityLocker() != address(0)); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /contracts/test/StakeLockerCustodial.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | import "./user/Custodian.sol"; 8 | 9 | contract StakeLockerCustodialTest is TestUtil { 10 | 11 | using SafeMath for uint256; 12 | 13 | function setUp() public { 14 | setUpGlobals(); 15 | setUpTokens(); 16 | setUpOracles(); 17 | setUpFactories(); 18 | setUpCalcs(); 19 | setUpActors(); 20 | createBalancerPool(); 21 | transferBptsToPoolDelegateAndStakers(); 22 | setUpLiquidityPool(); 23 | } 24 | 25 | function test_custody_and_transfer(uint256 stakeAmt, uint256 custodyAmt1, uint256 custodyAmt2) public { 26 | Custodian custodian1 = new Custodian(); // Custodial contract for StakeLockerFDTs - will start out as liquidity mining but could be broader DeFi eventually 27 | Custodian custodian2 = new Custodian(); // Custodial contract for StakeLockerFDTs - will start out as liquidity mining but could be broader DeFi eventually 28 | 29 | stakeAmt = constrictToRange(stakeAmt, 100, bPool.balanceOf(address(sam)), true); // 100 wei - whole BPT bal (Sid and Sam have the same BPT balance) 30 | custodyAmt1 = constrictToRange(custodyAmt1, 40, stakeAmt / 2, true); // 40 wei - half of stake 31 | custodyAmt2 = constrictToRange(custodyAmt2, 40, stakeAmt / 2, true); // 40 wei - half of stake 32 | 33 | // Make StakeLocker public and stake tokens 34 | pat.openStakeLockerToPublic(address(stakeLocker)); 35 | sam.approve(address(bPool), address(stakeLocker), stakeAmt); 36 | sid.approve(address(bPool), address(stakeLocker), stakeAmt); 37 | sam.stake(address(stakeLocker), stakeAmt); 38 | sid.stake(address(stakeLocker), stakeAmt); 39 | 40 | pat.setStakeLockerLockupPeriod(address(stakeLocker), 0); 41 | 42 | // Testing failure modes with Sam 43 | assertTrue(!sam.try_increaseCustodyAllowance(address(stakeLocker), address(0), stakeAmt)); // P:INVALID_ADDRESS 44 | assertTrue(!sam.try_increaseCustodyAllowance(address(stakeLocker), address(custodian1), 0)); // P:INVALID_AMT 45 | assertTrue(!sam.try_increaseCustodyAllowance(address(stakeLocker), address(custodian1), stakeAmt + 1)); // P:INSUF_BALANCE 46 | assertTrue( sam.try_increaseCustodyAllowance(address(stakeLocker), address(custodian1), stakeAmt)); // Sam can custody entire balance 47 | 48 | // Testing state transition and transfers with Sid 49 | assertEq(stakeLocker.custodyAllowance(address(sid), address(custodian1)), 0); 50 | assertEq(stakeLocker.totalCustodyAllowance(address(sid)), 0); 51 | 52 | sid.increaseCustodyAllowance(address(stakeLocker), address(custodian1), custodyAmt1); 53 | 54 | assertEq(stakeLocker.custodyAllowance(address(sid), address(custodian1)), custodyAmt1); // Sid gives custody to custodian 1 55 | assertEq(stakeLocker.totalCustodyAllowance(address(sid)), custodyAmt1); // Total custody allowance goes up 56 | 57 | sid.increaseCustodyAllowance(address(stakeLocker), address(custodian2), custodyAmt2); 58 | 59 | assertEq(stakeLocker.custodyAllowance(address(sid), address(custodian2)), custodyAmt2); // Sid gives custody to custodian 2 60 | assertEq(stakeLocker.totalCustodyAllowance(address(sid)), custodyAmt1 + custodyAmt2); // Total custody allowance goes up 61 | 62 | uint256 transferableAmt = stakeAmt - custodyAmt1 - custodyAmt2; 63 | 64 | assertEq(stakeLocker.balanceOf(address(sid)), stakeAmt); 65 | assertEq(stakeLocker.balanceOf(address(sue)), 0); 66 | 67 | assertTrue(!sid.try_transfer(address(stakeLocker), address(sue), transferableAmt + 1)); // Sid cannot transfer more than balance - totalCustodyAllowance 68 | assertTrue( sid.try_transfer(address(stakeLocker), address(sue), transferableAmt)); // Sid can transfer transferableAmt 69 | 70 | assertEq(stakeLocker.balanceOf(address(sid)), stakeAmt - transferableAmt); 71 | assertEq(stakeLocker.balanceOf(address(sue)), transferableAmt); 72 | } 73 | 74 | function test_custody_and_unstake(uint256 stakeAmt, uint256 custodyAmt) public { 75 | Custodian custodian = new Custodian(); 76 | 77 | uint256 startingBptBal = bPool.balanceOf(address(sam)); 78 | 79 | stakeAmt = constrictToRange(stakeAmt, 1, startingBptBal, true); // 1 wei - whole BPT bal 80 | custodyAmt = constrictToRange(custodyAmt, 1, stakeAmt, true); // 1 wei - stakeAmt 81 | 82 | // Make StakeLocker public and stake tokens 83 | pat.openStakeLockerToPublic(address(stakeLocker)); 84 | sam.approve(address(bPool), address(stakeLocker), stakeAmt); 85 | sam.stake(address(stakeLocker), stakeAmt); 86 | 87 | pat.setStakeLockerLockupPeriod(address(stakeLocker), 0); 88 | 89 | assertEq(stakeLocker.custodyAllowance(address(sam), address(custodian)), 0); 90 | assertEq(stakeLocker.totalCustodyAllowance(address(sam)), 0); 91 | 92 | sam.increaseCustodyAllowance(address(stakeLocker), address(custodian), custodyAmt); 93 | 94 | assertEq(stakeLocker.custodyAllowance(address(sam), address(custodian)), custodyAmt); 95 | assertEq(stakeLocker.totalCustodyAllowance(address(sam)), custodyAmt); 96 | 97 | uint256 unstakeableAmt = stakeAmt - custodyAmt; 98 | 99 | assertEq(stakeLocker.balanceOf(address(sam)), stakeAmt); 100 | 101 | make_unstakeable(sam, stakeLocker); 102 | 103 | assertTrue(!sam.try_unstake(address(stakeLocker), unstakeableAmt + 1)); 104 | assertTrue( sam.try_unstake(address(stakeLocker), unstakeableAmt)); 105 | 106 | assertEq(stakeLocker.balanceOf(address(sam)), custodyAmt); 107 | assertEq(bPool.balanceOf(address(sam)), startingBptBal - custodyAmt); 108 | } 109 | 110 | function test_transferByCustodian(uint256 stakeAmt, uint256 custodyAmt) public { 111 | Custodian custodian = new Custodian(); // Custodial contract for PoolFDTs - will start out as liquidity mining but could be broader DeFi eventually 112 | 113 | stakeAmt = constrictToRange(stakeAmt, 1, bPool.balanceOf(address(sam)), true); // 1 wei - whole BPT bal 114 | custodyAmt = constrictToRange(custodyAmt, 1, stakeAmt, true); // 1 wei - stakeAmt 115 | 116 | // Make StakeLocker public and stake tokens 117 | pat.openStakeLockerToPublic(address(stakeLocker)); 118 | sam.approve(address(bPool), address(stakeLocker), stakeAmt); 119 | sam.stake(address(stakeLocker), stakeAmt); 120 | 121 | sam.increaseCustodyAllowance(address(stakeLocker), address(custodian), custodyAmt); 122 | 123 | assertEq(stakeLocker.custodyAllowance(address(sam), address(custodian)), custodyAmt); // Sam gives custody to custodian 124 | assertEq(stakeLocker.totalCustodyAllowance(address(sam)), custodyAmt); // Total custody allowance goes up 125 | 126 | assertTrue(!custodian.try_transferByCustodian(address(stakeLocker), address(sam), address(sid), custodyAmt)); // P:INVALID_RECEIVER 127 | assertTrue(!custodian.try_transferByCustodian(address(stakeLocker), address(sam), address(sam), 0)); // P:INVALID_AMT 128 | assertTrue(!custodian.try_transferByCustodian(address(stakeLocker), address(sam), address(sam), custodyAmt + 1)); // P:INSUF_ALLOWANCE 129 | assertTrue( custodian.try_transferByCustodian(address(stakeLocker), address(sam), address(sam), custodyAmt)); // Able to transfer custody amount back 130 | 131 | assertEq(stakeLocker.custodyAllowance(address(sam), address(custodian)), 0); // Custodian allowance has been reduced 132 | assertEq(stakeLocker.totalCustodyAllowance(address(sam)), 0); // Total custody allowance has been reduced, giving Sam access to funds again 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /contracts/test/StakeLockerFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./TestUtil.sol"; 6 | 7 | contract StakeLockerFactoryTest is TestUtil { 8 | 9 | function setUp() public { 10 | setUpGlobals(); 11 | createStakeLockerFactory(); 12 | } 13 | 14 | function test_newLocker() public { 15 | StakeLocker sl = StakeLocker(slFactory.newLocker(address(mpl), USDC)); 16 | 17 | // Validate the storage of slfactory. 18 | assertEq(slFactory.owner(address(sl)), address(this)); 19 | assertTrue(slFactory.isLocker(address(sl))); 20 | 21 | // Validate the storage of sl. 22 | assertEq(address(sl.stakeAsset()), address(mpl), "Incorrect stake asset address"); 23 | assertEq(sl.liquidityAsset(), USDC, "Incorrect address of loan asset"); 24 | assertEq(sl.pool(), address(this), "Incorrect pool address"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/test/interfaces/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.11; 2 | 3 | interface IUniswapV2Factory { 4 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 5 | 6 | function feeTo() external view returns (address); 7 | function feeToSetter() external view returns (address); 8 | 9 | function getPair(address tokenA, address tokenB) external view returns (address pair); 10 | function allPairs(uint) external view returns (address pair); 11 | function allPairsLength() external view returns (uint); 12 | 13 | function createPair(address tokenA, address tokenB) external returns (address pair); 14 | 15 | function setFeeTo(address) external; 16 | function setFeeToSetter(address) external; 17 | } -------------------------------------------------------------------------------- /contracts/test/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.11; 2 | 3 | interface IUniswapV2Pair { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external pure returns (string memory); 8 | function symbol() external pure returns (string memory); 9 | function decimals() external pure returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | 18 | function DOMAIN_SEPARATOR() external view returns (bytes32); 19 | function PERMIT_TYPEHASH() external pure returns (bytes32); 20 | function nonces(address owner) external view returns (uint); 21 | 22 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 23 | 24 | event Mint(address indexed sender, uint amount0, uint amount1); 25 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 26 | event Swap( 27 | address indexed sender, 28 | uint amount0In, 29 | uint amount1In, 30 | uint amount0Out, 31 | uint amount1Out, 32 | address indexed to 33 | ); 34 | event Sync(uint112 reserve0, uint112 reserve1); 35 | 36 | function MINIMUM_LIQUIDITY() external pure returns (uint); 37 | function factory() external view returns (address); 38 | function token0() external view returns (address); 39 | function token1() external view returns (address); 40 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 41 | function price0CumulativeLast() external view returns (uint); 42 | function price1CumulativeLast() external view returns (uint); 43 | function kLast() external view returns (uint); 44 | 45 | function mint(address to) external returns (uint liquidity); 46 | function burn(address to) external returns (uint amount0, uint amount1); 47 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 48 | function skim(address to) external; 49 | function sync() external; 50 | 51 | function initialize(address, address) external; 52 | } -------------------------------------------------------------------------------- /contracts/test/interfaces/IUniswapV2Router02.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.11; 2 | 3 | interface IUniswapV2Router02 { 4 | function factory() external pure returns (address); 5 | function WETH() external pure returns (address); 6 | 7 | function addLiquidity( 8 | address tokenA, 9 | address tokenB, 10 | uint amountADesired, 11 | uint amountBDesired, 12 | uint amountAMin, 13 | uint amountBMin, 14 | address to, 15 | uint deadline 16 | ) external returns (uint amountA, uint amountB, uint liquidity); 17 | function addLiquidityETH( 18 | address token, 19 | uint amountTokenDesired, 20 | uint amountTokenMin, 21 | uint amountETHMin, 22 | address to, 23 | uint deadline 24 | ) external payable returns (uint amountToken, uint amountETH, uint liquidity); 25 | function removeLiquidity( 26 | address tokenA, 27 | address tokenB, 28 | uint liquidity, 29 | uint amountAMin, 30 | uint amountBMin, 31 | address to, 32 | uint deadline 33 | ) external returns (uint amountA, uint amountB); 34 | function removeLiquidityETH( 35 | address token, 36 | uint liquidity, 37 | uint amountTokenMin, 38 | uint amountETHMin, 39 | address to, 40 | uint deadline 41 | ) external returns (uint amountToken, uint amountETH); 42 | function removeLiquidityWithPermit( 43 | address tokenA, 44 | address tokenB, 45 | uint liquidity, 46 | uint amountAMin, 47 | uint amountBMin, 48 | address to, 49 | uint deadline, 50 | bool approveMax, uint8 v, bytes32 r, bytes32 s 51 | ) external returns (uint amountA, uint amountB); 52 | function removeLiquidityETHWithPermit( 53 | address token, 54 | uint liquidity, 55 | uint amountTokenMin, 56 | uint amountETHMin, 57 | address to, 58 | uint deadline, 59 | bool approveMax, uint8 v, bytes32 r, bytes32 s 60 | ) external returns (uint amountToken, uint amountETH); 61 | function swapExactTokensForTokens( 62 | uint amountIn, 63 | uint amountOutMin, 64 | address[] calldata path, 65 | address to, 66 | uint deadline 67 | ) external returns (uint[] memory amounts); 68 | function swapTokensForExactTokens( 69 | uint amountOut, 70 | uint amountInMax, 71 | address[] calldata path, 72 | address to, 73 | uint deadline 74 | ) external returns (uint[] memory amounts); 75 | function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) 76 | external 77 | payable 78 | returns (uint[] memory amounts); 79 | function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) 80 | external 81 | returns (uint[] memory amounts); 82 | function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) 83 | external 84 | returns (uint[] memory amounts); 85 | function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) 86 | external 87 | payable 88 | returns (uint[] memory amounts); 89 | 90 | function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); 91 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); 92 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); 93 | function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); 94 | function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); 95 | } -------------------------------------------------------------------------------- /contracts/test/user/Borrower.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../Loan.sol"; 6 | 7 | import "../../interfaces/IStakeLocker.sol"; 8 | import "../../interfaces/ILoan.sol"; 9 | import "../../interfaces/ILoanFactory.sol"; 10 | 11 | import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 12 | 13 | contract Borrower { 14 | 15 | /************************/ 16 | /*** DIRECT FUNCTIONS ***/ 17 | /************************/ 18 | 19 | function pause(address loan) external { 20 | ILoan(loan).pause(); 21 | } 22 | 23 | function unpause(address loan) external { 24 | ILoan(loan).unpause(); 25 | } 26 | 27 | function makePayment(address loan) external { 28 | ILoan(loan).makePayment(); 29 | } 30 | 31 | function makeFullPayment(address loan) external { 32 | ILoan(loan).makeFullPayment(); 33 | } 34 | 35 | function drawdown(address loan, uint256 _drawdownAmount) external { 36 | ILoan(loan).drawdown(_drawdownAmount); 37 | } 38 | 39 | function approve(address token, address account, uint256 amt) external { 40 | IERC20(token).approve(account, amt); 41 | } 42 | 43 | function createLoan( 44 | address loanFactory, 45 | address liquidityAsset, 46 | address collateralAsset, 47 | address flFactory, 48 | address clFactory, 49 | uint256[5] memory specs, 50 | address[3] memory calcs 51 | ) 52 | external returns (Loan loan) 53 | { 54 | loan = Loan( 55 | ILoanFactory(loanFactory).createLoan(liquidityAsset, collateralAsset, flFactory, clFactory, specs, calcs) 56 | ); 57 | } 58 | 59 | function triggerDefault(address loan) external { 60 | ILoan(loan).triggerDefault(); 61 | } 62 | 63 | 64 | /*********************/ 65 | /*** TRY FUNCTIONS ***/ 66 | /*********************/ 67 | 68 | function try_createLoan( 69 | address loanFactory, 70 | address liquidityAsset, 71 | address collateralAsset, 72 | address flFactory, 73 | address clFactory, 74 | uint256[5] memory specs, 75 | address[3] memory calcs 76 | ) 77 | external returns (bool ok) 78 | { 79 | string memory sig = "createLoan(address,address,address,address,uint256[5],address[3])"; 80 | (ok,) = address(loanFactory).call( 81 | abi.encodeWithSignature(sig, liquidityAsset, collateralAsset, flFactory, clFactory, specs, calcs) 82 | ); 83 | } 84 | 85 | function try_drawdown(address loan, uint256 amt) external returns (bool ok) { 86 | string memory sig = "drawdown(uint256)"; 87 | (ok,) = address(loan).call(abi.encodeWithSignature(sig, amt)); 88 | } 89 | 90 | function try_makePayment(address loan) external returns (bool ok) { 91 | string memory sig = "makePayment()"; 92 | (ok,) = address(loan).call(abi.encodeWithSignature(sig)); 93 | } 94 | 95 | function try_makeFullPayment(address loan) external returns (bool ok) { 96 | string memory sig = "makeFullPayment()"; 97 | (ok,) = address(loan).call(abi.encodeWithSignature(sig)); 98 | } 99 | 100 | function try_unwind(address loan) external returns (bool ok) { 101 | string memory sig = "unwind()"; 102 | (ok,) = address(loan).call(abi.encodeWithSignature(sig)); 103 | } 104 | 105 | function try_pull(address locker, address dst, uint256 amt) external returns (bool ok) { 106 | string memory sig = "pull(address,uint256)"; 107 | (ok,) = address(locker).call(abi.encodeWithSignature(sig, dst, amt)); 108 | } 109 | 110 | function try_setLoanAdmin(address loan, address newLoanAdmin, bool status) external returns (bool ok) { 111 | string memory sig = "setLoanAdmin(address,bool)"; 112 | (ok,) = address(loan).call(abi.encodeWithSignature(sig, newLoanAdmin, status)); 113 | } 114 | 115 | function try_pause(address target) external returns (bool ok) { 116 | string memory sig = "pause()"; 117 | (ok,) = target.call(abi.encodeWithSignature(sig)); 118 | } 119 | 120 | function try_unpause(address target) external returns (bool ok) { 121 | string memory sig = "unpause()"; 122 | (ok,) = target.call(abi.encodeWithSignature(sig)); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /contracts/test/user/Commoner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | contract Commoner { 6 | 7 | function try_setLiquidityCap(address pool, uint256 liquidityCap) external returns (bool ok) { 8 | string memory sig = "setLiquidityCap(uint256)"; 9 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, liquidityCap)); 10 | } 11 | 12 | function try_triggerDefault(address loan) external returns (bool ok) { 13 | string memory sig = "triggerDefault()"; 14 | (ok,) = loan.call(abi.encodeWithSignature(sig)); 15 | } 16 | 17 | function try_setProtocolPause(address globals, bool pause) external returns (bool ok) { 18 | string memory sig = "setProtocolPause(bool)"; 19 | (ok,) = globals.call(abi.encodeWithSignature(sig, pause)); 20 | } 21 | 22 | function try_setManualOverride(address oracle, bool _override) external returns (bool ok) { 23 | string memory sig = "setManualOverride(bool)"; 24 | (ok,) = oracle.call(abi.encodeWithSignature(sig, _override)); 25 | } 26 | 27 | function try_setManualPrice(address oracle, int256 priceFeed) external returns (bool ok) { 28 | string memory sig = "setManualPrice(int256)"; 29 | (ok,) = oracle.call(abi.encodeWithSignature(sig, priceFeed)); 30 | } 31 | 32 | function try_changeAggregator(address oracle, address aggregator) external returns (bool ok) { 33 | string memory sig = "changeAggregator(address)"; 34 | (ok,) = oracle.call(abi.encodeWithSignature(sig, aggregator)); 35 | } 36 | 37 | function try_pause(address target) external returns (bool ok) { 38 | string memory sig = "pause()"; 39 | (ok,) = target.call(abi.encodeWithSignature(sig)); 40 | } 41 | 42 | function try_unpause(address target) external returns (bool ok) { 43 | string memory sig = "unpause()"; 44 | (ok,) = target.call(abi.encodeWithSignature(sig)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/test/user/Custodian.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../interfaces/IPool.sol"; 6 | 7 | contract Custodian { 8 | 9 | /************************/ 10 | /*** DIRECT FUNCTIONS ***/ 11 | /************************/ 12 | 13 | function transferByCustodian(address erc2258, address from, address to, uint256 amt) external { 14 | IPool(erc2258).transferByCustodian(from, to, amt); 15 | } 16 | 17 | /*********************/ 18 | /*** TRY FUNCTIONS ***/ 19 | /*********************/ 20 | 21 | function try_transferByCustodian(address erc2258, address from, address to, uint256 amt) external returns (bool ok) { 22 | string memory sig = "transferByCustodian(address,address,uint256)"; 23 | (ok,) = address(erc2258).call(abi.encodeWithSignature(sig, from, to, amt)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/user/EmergencyAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | contract EmergencyAdmin { 6 | function try_setProtocolPause(address globals, bool pause) external returns (bool ok) { 7 | string memory sig = "setProtocolPause(bool)"; 8 | (ok,) = globals.call(abi.encodeWithSignature(sig, pause)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /contracts/test/user/Farmer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./LP.sol"; 5 | 6 | import "../../MplRewards.sol"; 7 | 8 | import "../../interfaces/IPool.sol"; 9 | 10 | contract Farmer is LP { 11 | 12 | MplRewards public mplRewards; 13 | IERC20 public poolFDT; 14 | 15 | constructor(MplRewards _mplRewards, IERC20 _poolFDT) public { 16 | mplRewards = _mplRewards; 17 | poolFDT = _poolFDT; 18 | } 19 | 20 | /************************/ 21 | /*** DIRECT FUNCTIONS ***/ 22 | /************************/ 23 | 24 | function approve(address account, uint256 amt) public { 25 | poolFDT.approve(account, amt); 26 | } 27 | 28 | function increaseCustodyAllowance(address pool, address account, uint256 amt) public { 29 | IPool(pool).increaseCustodyAllowance(account, amt); 30 | } 31 | 32 | function transfer(address asset, address to, uint256 amt) public { 33 | IERC20(asset).transfer(to, amt); 34 | } 35 | 36 | function stake(uint256 amt) public { 37 | mplRewards.stake(amt); 38 | } 39 | 40 | function withdraw(uint256 amt) public { 41 | mplRewards.withdraw(amt); 42 | } 43 | 44 | function getReward() public { 45 | mplRewards.getReward(); 46 | } 47 | 48 | function exit() public { 49 | mplRewards.exit(); 50 | } 51 | 52 | /*********************/ 53 | /*** TRY FUNCTIONS ***/ 54 | /*********************/ 55 | 56 | function try_stake(uint256 amt) external returns (bool ok) { 57 | string memory sig = "stake(uint256)"; 58 | (ok,) = address(mplRewards).call(abi.encodeWithSignature(sig, amt)); 59 | } 60 | 61 | function try_withdraw(uint256 amt) external returns (bool ok) { 62 | string memory sig = "withdraw(uint256)"; 63 | (ok,) = address(mplRewards).call(abi.encodeWithSignature(sig, amt)); 64 | } 65 | 66 | function try_increaseCustodyAllowance(address pool, address account, uint256 amt) external returns (bool ok) { 67 | string memory sig = "increaseCustodyAllowance(address,uint256)"; 68 | (ok,) = pool.call(abi.encodeWithSignature(sig, account, amt)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/test/user/Holder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../token/interfaces/IBaseFDT.sol"; 6 | 7 | contract Holder { 8 | function withdrawFunds(address token) external { 9 | IBaseFDT(token).withdrawFunds(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/test/user/LP.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../interfaces/IPool.sol"; 6 | 7 | import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 8 | 9 | contract LP { 10 | 11 | /************************/ 12 | /*** DIRECT FUNCTIONS ***/ 13 | /************************/ 14 | 15 | function approve(address token, address account, uint256 amt) external { 16 | IERC20(token).approve(account, amt); 17 | } 18 | 19 | function withdraw(address pool, uint256 amt) external { 20 | IPool(pool).withdraw(amt); 21 | } 22 | 23 | function deposit(address pool, uint256 amt) external { 24 | IPool(pool).deposit(amt); 25 | } 26 | 27 | function transferFDT(address pool, address account, uint256 amt) external { 28 | IPool(pool).transfer(account, amt); 29 | } 30 | 31 | function withdrawFunds(address pool) external { 32 | IPool(pool).withdrawFunds(); 33 | } 34 | 35 | function claim(address pool, address loan, address dlFactory) external { IPool(pool).claim(loan, dlFactory); } 36 | 37 | function intendToWithdraw(address pool) external { IPool(pool).intendToWithdraw(); } 38 | 39 | /*********************/ 40 | /*** TRY FUNCTIONS ***/ 41 | /*********************/ 42 | 43 | function try_deposit(address pool1, uint256 amt) external returns (bool ok) { 44 | string memory sig = "deposit(uint256)"; 45 | (ok,) = address(pool1).call(abi.encodeWithSignature(sig, amt)); 46 | } 47 | 48 | function try_withdraw(address pool, uint256 amt) external returns (bool ok) { 49 | string memory sig = "withdraw(uint256)"; 50 | (ok,) = pool.call(abi.encodeWithSignature(sig, amt)); 51 | } 52 | 53 | function try_withdrawFunds(address pool) external returns (bool ok) { 54 | string memory sig = "withdrawFunds()"; 55 | (ok,) = pool.call(abi.encodeWithSignature(sig)); 56 | } 57 | 58 | function try_claim(address pool, address loan, address dlFactory) external returns (bool ok) { 59 | string memory sig = "claim(address,address)"; 60 | (ok,) = pool.call(abi.encodeWithSignature(sig, loan, dlFactory)); 61 | } 62 | 63 | function try_intendToWithdraw(address pool) external returns (bool ok) { 64 | string memory sig = "intendToWithdraw()"; 65 | (ok,) = pool.call(abi.encodeWithSignature(sig)); 66 | } 67 | 68 | function try_cancelWithdraw(address pool) external returns (bool ok) { 69 | string memory sig = "cancelWithdraw()"; 70 | (ok,) = pool.call(abi.encodeWithSignature(sig)); 71 | } 72 | 73 | function try_transfer(address pool, address account, uint256 amt) external returns (bool ok) { 74 | string memory sig = "transfer(address,uint256)"; 75 | (ok,) = pool.call(abi.encodeWithSignature(sig, account, amt)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /contracts/test/user/Lender.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../interfaces/ILoan.sol"; 6 | 7 | import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 8 | 9 | contract Lender { 10 | 11 | /************************/ 12 | /*** DIRECT FUNCTIONS ***/ 13 | /************************/ 14 | 15 | function fundLoan(address loan, uint256 amt, address account) external { 16 | ILoan(loan).fundLoan(account, amt); 17 | } 18 | 19 | function approve(address token, address account, uint256 amt) external { 20 | IERC20(token).approve(account, amt); 21 | } 22 | 23 | function transfer(address token, address account, uint256 amt) external { 24 | IERC20(token).transfer(account, amt); 25 | } 26 | 27 | function triggerDefault(address loan) public { 28 | ILoan(loan).triggerDefault(); 29 | } 30 | 31 | function withdrawFunds(address loan) public { 32 | ILoan(loan).withdrawFunds(); 33 | } 34 | 35 | 36 | /*********************/ 37 | /*** TRY FUNCTIONS ***/ 38 | /*********************/ 39 | 40 | // To assert failures 41 | function try_drawdown(address loan, uint256 amt) external returns (bool ok) { 42 | string memory sig = "drawdown(uint256)"; 43 | (ok,) = address(loan).call(abi.encodeWithSignature(sig, amt)); 44 | } 45 | 46 | function try_fundLoan(address loan, address mintTo, uint256 amt) external returns (bool ok) { 47 | string memory sig = "fundLoan(address,uint256)"; 48 | (ok,) = address(loan).call(abi.encodeWithSignature(sig, mintTo, amt)); 49 | } 50 | 51 | function try_trigger_default(address loan) external returns (bool ok) { 52 | string memory sig = "triggerDefault()"; 53 | (ok,) = loan.call(abi.encodeWithSignature(sig)); 54 | } 55 | 56 | function try_withdrawFunds(address loan) external returns (bool ok) { 57 | string memory sig = "withdrawFunds()"; 58 | (ok,) = loan.call(abi.encodeWithSignature(sig)); 59 | } 60 | 61 | function try_triggerDefault(address loan) external returns (bool ok) { 62 | string memory sig = "triggerDefault()"; 63 | (ok,) = address(loan).call(abi.encodeWithSignature(sig)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/test/user/PoolDelegate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../interfaces/IBPool.sol"; 6 | import "../../interfaces/ILoan.sol"; 7 | import "../../interfaces/IPool.sol"; 8 | import "../../interfaces/IPoolFactory.sol"; 9 | import "../../interfaces/IStakeLocker.sol"; 10 | 11 | import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 12 | 13 | contract PoolDelegate { 14 | 15 | /************************/ 16 | /*** DIRECT FUNCTIONS ***/ 17 | /************************/ 18 | 19 | function createPool( 20 | address poolFactory, 21 | address liquidityAsset, 22 | address stakeAsset, 23 | address slFactory, 24 | address llFactory, 25 | uint256 stakingFee, 26 | uint256 delegateFee, 27 | uint256 liquidityCap 28 | ) 29 | external returns (address liquidityPool) 30 | { 31 | liquidityPool = IPoolFactory(poolFactory).createPool( 32 | liquidityAsset, 33 | stakeAsset, 34 | slFactory, 35 | llFactory, 36 | stakingFee, 37 | delegateFee, 38 | liquidityCap 39 | ); 40 | } 41 | 42 | function approve(address token, address account, uint256 amt) external { 43 | IERC20(token).approve(account, amt); 44 | } 45 | 46 | function stake(address stakeLocker, uint256 amt) external { 47 | IStakeLocker(stakeLocker).stake(amt); 48 | } 49 | 50 | function finalize(address pool) external { 51 | IPool(pool).finalize(); 52 | } 53 | 54 | function unstake(address stakeLocker, uint256 amt) external { 55 | IStakeLocker(stakeLocker).unstake(amt); 56 | } 57 | 58 | function fundLoan(address pool, address loan, address dlFactory, uint256 amt) external { 59 | IPool(pool).fundLoan(loan, dlFactory, amt); 60 | } 61 | 62 | function unwind(address loan) external { 63 | ILoan(loan).unwind(); 64 | } 65 | 66 | function claim(address pool, address loan, address dlFactory) external returns (uint256[7] memory) { 67 | return IPool(pool).claim(loan, dlFactory); 68 | } 69 | 70 | function deactivate(address pool) external { 71 | IPool(pool).deactivate(); 72 | } 73 | 74 | function triggerDefault(address pool, address loan, address dlFactory) external { 75 | IPool(pool).triggerDefault(loan, dlFactory); 76 | } 77 | 78 | function setPoolAdmin(address pool, address newPoolAdmin, bool status) external { 79 | IPool(pool).setPoolAdmin(newPoolAdmin, status); 80 | } 81 | 82 | function setOpenToPublic(address pool, bool open) external { 83 | IPool(pool).setOpenToPublic(open); 84 | } 85 | 86 | function setLockupPeriod(address pool, uint256 lockupPeriod) external { 87 | IPool(pool).setLockupPeriod(lockupPeriod); 88 | } 89 | 90 | function setStakeLockerLockupPeriod(address stakeLocker, uint256 lockupPeriod) external { 91 | IStakeLocker(stakeLocker).setLockupPeriod(lockupPeriod); 92 | } 93 | 94 | function setStakingFee(address pool, uint256 stakingFee) external { 95 | IPool(pool).setStakingFee(stakingFee); 96 | } 97 | 98 | function setAllowList(address pool, address account, bool status) external { 99 | IPool(pool).setAllowList(account, status); 100 | } 101 | 102 | function openStakeLockerToPublic(address stakeLocker) external { 103 | IStakeLocker(stakeLocker).openStakeLockerToPublic(); 104 | } 105 | 106 | function setAllowlist(address stakeLocker, address account, bool status) external { 107 | IStakeLocker(stakeLocker).setAllowlist(account, status); 108 | } 109 | 110 | // Balancer Pool 111 | function joinBPool(IBPool bPool, uint poolAmountOut, uint[] calldata maxAmountsIn) external { 112 | bPool.joinPool(poolAmountOut, maxAmountsIn); 113 | } 114 | 115 | /*********************/ 116 | /*** TRY FUNCTIONS ***/ 117 | /*********************/ 118 | 119 | function try_createPool( 120 | address poolFactory, 121 | address liquidityAsset, 122 | address stakeAsset, 123 | address slFactory, 124 | address llFactory, 125 | uint256 stakingFee, 126 | uint256 delegateFee, 127 | uint256 liquidityCap 128 | ) 129 | external returns (bool ok) 130 | { 131 | string memory sig = "createPool(address,address,address,address,uint256,uint256,uint256)"; 132 | (ok,) = address(poolFactory).call( 133 | abi.encodeWithSignature(sig, liquidityAsset, stakeAsset, slFactory, llFactory, stakingFee, delegateFee, liquidityCap) 134 | ); 135 | } 136 | 137 | function try_fundLoan(address pool, address loan, address dlFactory, uint256 amt) external returns (bool ok) { 138 | string memory sig = "fundLoan(address,address,uint256)"; 139 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, loan, dlFactory, amt)); 140 | } 141 | 142 | function try_unwind(address loan) external returns (bool ok) { 143 | string memory sig = "unwind()"; 144 | (ok,) = address(loan).call(abi.encodeWithSignature(sig)); 145 | } 146 | 147 | function try_claim(address pool, address loan, address dlFactory) external returns (bool ok) { 148 | string memory sig = "claim(address,address)"; 149 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, loan, dlFactory)); 150 | } 151 | 152 | function try_finalize(address pool) external returns (bool ok) { 153 | string memory sig = "finalize()"; 154 | (ok,) = address(pool).call(abi.encodeWithSignature(sig)); 155 | } 156 | 157 | function try_deactivate(address pool) external returns (bool ok) { 158 | string memory sig = "deactivate()"; 159 | (ok,) = address(pool).call(abi.encodeWithSignature(sig)); 160 | } 161 | 162 | function try_setLiquidityCap(address pool, uint256 liquidityCap) external returns (bool ok) { 163 | string memory sig = "setLiquidityCap(uint256)"; 164 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, liquidityCap)); 165 | } 166 | 167 | function try_setLockupPeriod(address pool, uint256 newPeriod) external returns (bool ok) { 168 | string memory sig = "setLockupPeriod(uint256)"; 169 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, newPeriod)); 170 | } 171 | 172 | function try_setStakingFee(address pool, uint256 newStakingFee) external returns (bool ok) { 173 | string memory sig = "setStakingFee(uint256)"; 174 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, newStakingFee)); 175 | } 176 | 177 | function try_triggerDefault(address pool, address loan, address dlFactory) external returns (bool ok) { 178 | string memory sig = "triggerDefault(address,address)"; 179 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, loan, dlFactory)); 180 | } 181 | 182 | function try_setOpenToPublic(address pool, bool open) external returns (bool ok) { 183 | string memory sig = "setOpenToPublic(bool)"; 184 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, open)); 185 | } 186 | 187 | function try_openStakeLockerToPublic(address stakeLocker) external returns (bool ok) { 188 | string memory sig = "openStakeLockerToPublic()"; 189 | (ok,) = address(stakeLocker).call(abi.encodeWithSignature(sig)); 190 | } 191 | 192 | function try_setAllowList(address pool, address account, bool status) external returns (bool ok) { 193 | string memory sig = "setAllowList(address,bool)"; 194 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, account, status)); 195 | } 196 | 197 | function try_setAllowlist(address stakeLocker, address account, bool status) external returns (bool ok) { 198 | string memory sig = "setAllowlist(address,bool)"; 199 | (ok,) = address(stakeLocker).call(abi.encodeWithSignature(sig, account, status)); 200 | } 201 | 202 | function try_setPoolAdmin(address pool, address newPoolAdmin, bool status) external returns (bool ok) { 203 | string memory sig = "setPoolAdmin(address,bool)"; 204 | (ok,) = address(pool).call(abi.encodeWithSignature(sig, newPoolAdmin, status)); 205 | } 206 | 207 | function try_pause(address target) external returns (bool ok) { 208 | string memory sig = "pause()"; 209 | (ok,) = target.call(abi.encodeWithSignature(sig)); 210 | } 211 | 212 | function try_unpause(address target) external returns (bool ok) { 213 | string memory sig = "unpause()"; 214 | (ok,) = target.call(abi.encodeWithSignature(sig)); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /contracts/test/user/SecurityAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../interfaces/IOracle.sol"; 6 | import "../../interfaces/IPool.sol"; 7 | import "../../oracles/ChainlinkOracle.sol"; 8 | 9 | contract SecurityAdmin { 10 | 11 | function claim(address pool, address loan, address dlFactory) external { IPool(pool).claim(loan, dlFactory); } 12 | function setManualPrice(address target, int256 price) external { IOracle(target).setManualPrice(price); } 13 | function setManualOverride(address target, bool _override) external { IOracle(target).setManualOverride(_override); } 14 | function changeAggregator(address target, address aggregator) external { IOracle(target).changeAggregator(aggregator); } 15 | 16 | function try_claim(address pool, address loan, address dlFactory) external returns (bool ok) { 17 | string memory sig = "claim(address,address)"; 18 | (ok,) = pool.call(abi.encodeWithSignature(sig, loan, dlFactory)); 19 | } 20 | 21 | function try_setManualPrice(address oracle, int256 priceFeed) external returns (bool ok) { 22 | string memory sig = "setManualPrice(int256)"; 23 | (ok,) = oracle.call(abi.encodeWithSignature(sig, priceFeed)); 24 | } 25 | 26 | function try_setManualOverride(address oracle, bool _override) external returns (bool ok) { 27 | string memory sig = "setManualOverride(bool)"; 28 | (ok,) = oracle.call(abi.encodeWithSignature(sig, _override)); 29 | } 30 | 31 | function try_changeAggregator(address oracle, address aggregator) external returns (bool ok) { 32 | string memory sig = "changeAggregator(address)"; 33 | (ok,) = oracle.call(abi.encodeWithSignature(sig, aggregator)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/test/user/Staker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../interfaces/IBPool.sol"; 6 | import "../../interfaces/IStakeLocker.sol"; 7 | 8 | import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 9 | 10 | contract Staker { 11 | 12 | /************************/ 13 | /*** DIRECT FUNCTIONS ***/ 14 | /************************/ 15 | 16 | function approve(address token, address account, uint256 amt) external { 17 | IERC20(token).approve(account, amt); 18 | } 19 | 20 | function increaseCustodyAllowance(address stakeLocker, address account, uint256 amt) public { 21 | IStakeLocker(stakeLocker).increaseCustodyAllowance(account, amt); 22 | } 23 | 24 | function stake(address stakeLocker, uint256 amt) external { 25 | IStakeLocker(stakeLocker).stake(amt); 26 | } 27 | 28 | function unstake(address stakeLocker, uint256 amt) external { 29 | IStakeLocker(stakeLocker).unstake(amt); 30 | } 31 | 32 | function transfer(address token, address dst, uint256 amt) external { 33 | IERC20(token).transfer(dst, amt); 34 | } 35 | 36 | function intendToUnstake(address stakeLocker) external { IStakeLocker(stakeLocker).intendToUnstake(); } 37 | 38 | // Balancer Pool 39 | function joinBPool(IBPool bPool, uint poolAmountOut, uint[] calldata maxAmountsIn) external { 40 | bPool.joinPool(poolAmountOut, maxAmountsIn); 41 | } 42 | 43 | 44 | /*********************/ 45 | /*** TRY FUNCTIONS ***/ 46 | /*********************/ 47 | 48 | function try_stake(address stakeLocker, uint256 amt) external returns (bool ok) { 49 | string memory sig = "stake(uint256)"; 50 | (ok,) = address(stakeLocker).call(abi.encodeWithSignature(sig, amt)); 51 | } 52 | 53 | function try_unstake(address stakeLocker, uint256 amt) external returns (bool ok) { 54 | string memory sig = "unstake(uint256)"; 55 | (ok,) = address(stakeLocker).call(abi.encodeWithSignature(sig, amt)); 56 | } 57 | 58 | function try_transfer(address token, address dst, uint256 amt) external returns (bool ok) { 59 | string memory sig = "transfer(address,uint256)"; 60 | (ok,) = address(token).call(abi.encodeWithSignature(sig, dst, amt)); 61 | } 62 | 63 | function try_transferFrom(address token, address from, address to, uint256 amt) external returns (bool ok) { 64 | string memory sig = "transferFrom(address,address,uint256)"; 65 | (ok,) = address(token).call(abi.encodeWithSignature(sig, from, to, amt)); 66 | } 67 | 68 | function try_intendToUnstake(address stakeLocker) external returns (bool ok) { 69 | string memory sig = "intendToUnstake()"; 70 | (ok,) = stakeLocker.call(abi.encodeWithSignature(sig)); 71 | } 72 | 73 | function try_cancelUnstake(address stakeLocker) external returns (bool ok) { 74 | string memory sig = "cancelUnstake()"; 75 | (ok,) = stakeLocker.call(abi.encodeWithSignature(sig)); 76 | } 77 | 78 | function try_withdrawFunds(address stakeLocker) external returns (bool ok) { 79 | string memory sig = "withdrawFunds()"; 80 | (ok,) = stakeLocker.call(abi.encodeWithSignature(sig)); 81 | } 82 | 83 | function try_increaseCustodyAllowance(address stakeLocker, address account, uint256 amt) external returns (bool ok) { 84 | string memory sig = "increaseCustodyAllowance(address,uint256)"; 85 | (ok,) = stakeLocker.call(abi.encodeWithSignature(sig, account, amt)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /contracts/token/BasicFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 5 | import "lib/openzeppelin-contracts/contracts/math/SafeMath.sol"; 6 | import "lib/openzeppelin-contracts/contracts/math/SignedSafeMath.sol"; 7 | import "./interfaces/IBaseFDT.sol"; 8 | import "../math/SafeMathUint.sol"; 9 | import "../math/SafeMathInt.sol"; 10 | 11 | /// @title BasicFDT implements base level FDT functionality for accounting for revenues. 12 | abstract contract BasicFDT is IBaseFDT, ERC20 { 13 | using SafeMath for uint256; 14 | using SafeMathUint for uint256; 15 | using SignedSafeMath for int256; 16 | using SafeMathInt for int256; 17 | 18 | uint256 internal constant pointsMultiplier = 2 ** 128; 19 | uint256 internal pointsPerShare; 20 | 21 | mapping(address => int256) internal pointsCorrection; 22 | mapping(address => uint256) internal withdrawnFunds; 23 | 24 | event PointsPerShareUpdated(uint256 pointsPerShare); 25 | event PointsCorrectionUpdated(address indexed account, int256 pointsCorrection); 26 | 27 | constructor(string memory name, string memory symbol) ERC20(name, symbol) public { } 28 | 29 | /** 30 | @dev Distributes funds to token holders. 31 | @dev It reverts if the total supply of tokens is 0. 32 | @dev It emits a `FundsDistributed` event if the amount of received funds is greater than 0. 33 | @dev It emits a `PointsPerShareUpdated` event if the amount of received funds is greater than 0. 34 | About undistributed funds: 35 | In each distribution, there is a small amount of funds which do not get distributed, 36 | which is `(value pointsMultiplier) % totalSupply()`. 37 | With a well-chosen `pointsMultiplier`, the amount funds that are not getting distributed 38 | in a distribution can be less than 1 (base unit). 39 | We can actually keep track of the undistributed funds in a distribution 40 | and try to distribute it in the next distribution. 41 | */ 42 | function _distributeFunds(uint256 value) internal { 43 | require(totalSupply() > 0, "FDT:ZERO_SUPPLY"); 44 | 45 | if (value == 0) return; 46 | 47 | pointsPerShare = pointsPerShare.add(value.mul(pointsMultiplier) / totalSupply()); 48 | emit FundsDistributed(msg.sender, value); 49 | emit PointsPerShareUpdated(pointsPerShare); 50 | } 51 | 52 | /** 53 | @dev Prepares the withdrawal of funds. 54 | @dev It emits a `FundsWithdrawn` event if the amount of withdrawn funds is greater than 0. 55 | @return withdrawableDividend The amount of dividend funds that can be withdrawn. 56 | */ 57 | function _prepareWithdraw() internal returns (uint256 withdrawableDividend) { 58 | withdrawableDividend = withdrawableFundsOf(msg.sender); 59 | uint256 _withdrawnFunds = withdrawnFunds[msg.sender].add(withdrawableDividend); 60 | withdrawnFunds[msg.sender] = _withdrawnFunds; 61 | 62 | emit FundsWithdrawn(msg.sender, withdrawableDividend, _withdrawnFunds); 63 | } 64 | 65 | /** 66 | @dev Returns the amount of funds that an account can withdraw. 67 | @param _owner The address of a token holder. 68 | @return The amount funds that `_owner` can withdraw. 69 | */ 70 | function withdrawableFundsOf(address _owner) public view override returns (uint256) { 71 | return accumulativeFundsOf(_owner).sub(withdrawnFunds[_owner]); 72 | } 73 | 74 | /** 75 | @dev Returns the amount of funds that an account has withdrawn. 76 | @param _owner The address of a token holder. 77 | @return The amount of funds that `_owner` has withdrawn. 78 | */ 79 | function withdrawnFundsOf(address _owner) external view returns (uint256) { 80 | return withdrawnFunds[_owner]; 81 | } 82 | 83 | /** 84 | @dev Returns the amount of funds that an account has earned in total. 85 | @dev accumulativeFundsOf(_owner) = withdrawableFundsOf(_owner) + withdrawnFundsOf(_owner) 86 | = (pointsPerShare * balanceOf(_owner) + pointsCorrection[_owner]) / pointsMultiplier 87 | @param _owner The address of a token holder. 88 | @return The amount of funds that `_owner` has earned in total. 89 | */ 90 | function accumulativeFundsOf(address _owner) public view returns (uint256) { 91 | return 92 | pointsPerShare 93 | .mul(balanceOf(_owner)) 94 | .toInt256Safe() 95 | .add(pointsCorrection[_owner]) 96 | .toUint256Safe() / pointsMultiplier; 97 | } 98 | 99 | /** 100 | @dev Transfers tokens from one account to another. Updates pointsCorrection to keep funds unchanged. 101 | @dev It emits two `PointsCorrectionUpdated` events, one for the sender and one for the receiver. 102 | @param from The address to transfer from. 103 | @param to The address to transfer to. 104 | @param value The amount to be transferred. 105 | */ 106 | function _transfer( 107 | address from, 108 | address to, 109 | uint256 value 110 | ) internal virtual override { 111 | super._transfer(from, to, value); 112 | 113 | int256 _magCorrection = pointsPerShare.mul(value).toInt256Safe(); 114 | int256 pointsCorrectionFrom = pointsCorrection[from].add(_magCorrection); 115 | pointsCorrection[from] = pointsCorrectionFrom; 116 | int256 pointsCorrectionTo = pointsCorrection[to].sub(_magCorrection); 117 | pointsCorrection[to] = pointsCorrectionTo; 118 | 119 | emit PointsCorrectionUpdated(from, pointsCorrectionFrom); 120 | emit PointsCorrectionUpdated(to, pointsCorrectionTo); 121 | } 122 | 123 | /** 124 | @dev Mints tokens to an account. Updates pointsCorrection to keep funds unchanged. 125 | @param account The account that will receive the created tokens. 126 | @param value The amount that will be created. 127 | */ 128 | function _mint(address account, uint256 value) internal virtual override { 129 | super._mint(account, value); 130 | 131 | int256 _pointsCorrection = pointsCorrection[account].sub( 132 | (pointsPerShare.mul(value)).toInt256Safe() 133 | ); 134 | 135 | pointsCorrection[account] = _pointsCorrection; 136 | 137 | emit PointsCorrectionUpdated(account, _pointsCorrection); 138 | } 139 | 140 | /** 141 | @dev Burns an amount of the token of a given account. Updates pointsCorrection to keep funds unchanged. 142 | @dev It emits a `PointsCorrectionUpdated` event. 143 | @param account The account whose tokens will be burnt. 144 | @param value The amount that will be burnt. 145 | */ 146 | function _burn(address account, uint256 value) internal virtual override { 147 | super._burn(account, value); 148 | 149 | int256 _pointsCorrection = pointsCorrection[account].add( 150 | (pointsPerShare.mul(value)).toInt256Safe() 151 | ); 152 | 153 | pointsCorrection[account] = _pointsCorrection; 154 | 155 | emit PointsCorrectionUpdated(account, _pointsCorrection); 156 | } 157 | 158 | /** 159 | @dev Withdraws all available funds for a token holder. 160 | */ 161 | function withdrawFunds() public virtual override {} 162 | 163 | /** 164 | @dev Updates the current `fundsToken` balance and returns the difference of the new and previous `fundsToken` balance. 165 | @return A int256 representing the difference of the new and previous `fundsToken` balance. 166 | */ 167 | function _updateFundsTokenBalance() internal virtual returns (int256) {} 168 | 169 | /** 170 | @dev Registers a payment of funds in tokens. May be called directly after a deposit is made. 171 | @dev Calls _updateFundsTokenBalance(), whereby the contract computes the delta of the new and previous 172 | `fundsToken` balance and increments the total received funds (cumulative), by delta, by calling _distributeFunds(). 173 | */ 174 | function updateFundsReceived() public virtual { 175 | int256 newFunds = _updateFundsTokenBalance(); 176 | 177 | if (newFunds <= 0) return; 178 | 179 | _distributeFunds(newFunds.toUint256Safe()); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /contracts/token/ExtendedFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./BasicFDT.sol"; 5 | 6 | /// @title ExtendedFDT implements FDT functionality for accounting for losses. 7 | abstract contract ExtendedFDT is BasicFDT { 8 | using SafeMath for uint256; 9 | using SafeMathUint for uint256; 10 | using SignedSafeMath for int256; 11 | using SafeMathInt for int256; 12 | 13 | uint256 internal lossesPerShare; 14 | 15 | mapping(address => int256) internal lossesCorrection; 16 | mapping(address => uint256) internal recognizedLosses; 17 | 18 | event LossesPerShareUpdated(uint256 lossesPerShare); 19 | event LossesCorrectionUpdated(address indexed account, int256 lossesCorrection); 20 | 21 | /** 22 | @dev This event emits when new losses are distributed. 23 | @param by The address of the account that has distributed losses. 24 | @param lossesDistributed The amount of losses received for distribution. 25 | */ 26 | event LossesDistributed(address indexed by, uint256 lossesDistributed); 27 | 28 | /** 29 | @dev This event emits when distributed losses are recognized by a token holder. 30 | @param by The address of the receiver of losses. 31 | @param lossesRecognized The amount of losses that were recognized. 32 | @param totalLossesRecognized The total amount of losses that are recognized. 33 | */ 34 | event LossesRecognized(address indexed by, uint256 lossesRecognized, uint256 totalLossesRecognized); 35 | 36 | constructor(string memory name, string memory symbol) BasicFDT(name, symbol) public { } 37 | 38 | /** 39 | @dev Distributes losses to token holders. 40 | @dev It reverts if the total supply of tokens is 0. 41 | @dev It emits a `LossesDistributed` event if the amount of received losses is greater than 0. 42 | @dev It emits a `LossesPerShareUpdated` event if the amount of received losses is greater than 0. 43 | About undistributed losses: 44 | In each distribution, there is a small amount of losses which do not get distributed, 45 | which is `(value * pointsMultiplier) % totalSupply()`. 46 | With a well-chosen `pointsMultiplier`, the amount losses that are not getting distributed 47 | in a distribution can be less than 1 (base unit). 48 | We can actually keep track of the undistributed losses in a distribution 49 | and try to distribute it in the next distribution. 50 | */ 51 | function _distributeLosses(uint256 value) internal { 52 | require(totalSupply() > 0, "FDT:ZERO_SUPPLY"); 53 | 54 | if (value == 0) return; 55 | 56 | uint256 _lossesPerShare = lossesPerShare.add(value.mul(pointsMultiplier) / totalSupply()); 57 | lossesPerShare = _lossesPerShare; 58 | 59 | emit LossesDistributed(msg.sender, value); 60 | emit LossesPerShareUpdated(_lossesPerShare); 61 | } 62 | 63 | /** 64 | @dev Prepares losses for a withdrawal. 65 | @dev It emits a `LossesWithdrawn` event if the amount of withdrawn losses is greater than 0. 66 | @return recognizableDividend The amount of dividend losses that can be recognized. 67 | */ 68 | function _prepareLossesWithdraw() internal returns (uint256 recognizableDividend) { 69 | recognizableDividend = recognizableLossesOf(msg.sender); 70 | 71 | uint256 _recognizedLosses = recognizedLosses[msg.sender].add(recognizableDividend); 72 | recognizedLosses[msg.sender] = _recognizedLosses; 73 | 74 | emit LossesRecognized(msg.sender, recognizableDividend, _recognizedLosses); 75 | } 76 | 77 | /** 78 | @dev Returns the amount of losses that an address can withdraw. 79 | @param _owner The address of a token holder. 80 | @return The amount of losses that `_owner` can withdraw. 81 | */ 82 | function recognizableLossesOf(address _owner) public view returns (uint256) { 83 | return accumulativeLossesOf(_owner).sub(recognizedLosses[_owner]); 84 | } 85 | 86 | /** 87 | @dev Returns the amount of losses that an address has recognized. 88 | @param _owner The address of a token holder 89 | @return The amount of losses that `_owner` has recognized 90 | */ 91 | function recognizedLossesOf(address _owner) external view returns (uint256) { 92 | return recognizedLosses[_owner]; 93 | } 94 | 95 | /** 96 | @dev Returns the amount of losses that an address has earned in total. 97 | @dev accumulativeLossesOf(_owner) = recognizableLossesOf(_owner) + recognizedLossesOf(_owner) 98 | = (lossesPerShare * balanceOf(_owner) + lossesCorrection[_owner]) / pointsMultiplier 99 | @param _owner The address of a token holder 100 | @return The amount of losses that `_owner` has earned in total 101 | */ 102 | function accumulativeLossesOf(address _owner) public view returns (uint256) { 103 | return 104 | lossesPerShare 105 | .mul(balanceOf(_owner)) 106 | .toInt256Safe() 107 | .add(lossesCorrection[_owner]) 108 | .toUint256Safe() / pointsMultiplier; 109 | } 110 | 111 | /** 112 | @dev Transfers tokens from one account to another. Updates pointsCorrection to keep funds unchanged. 113 | @dev It emits two `LossesCorrectionUpdated` events, one for the sender and one for the receiver. 114 | @param from The address to transfer from. 115 | @param to The address to transfer to. 116 | @param value The amount to be transferred. 117 | */ 118 | function _transfer( 119 | address from, 120 | address to, 121 | uint256 value 122 | ) internal virtual override { 123 | super._transfer(from, to, value); 124 | 125 | int256 _lossesCorrection = lossesPerShare.mul(value).toInt256Safe(); 126 | int256 lossesCorrectionFrom = lossesCorrection[from].add(_lossesCorrection); 127 | lossesCorrection[from] = lossesCorrectionFrom; 128 | int256 lossesCorrectionTo = lossesCorrection[to].sub(_lossesCorrection); 129 | lossesCorrection[to] = lossesCorrectionTo; 130 | 131 | emit LossesCorrectionUpdated(from, lossesCorrectionFrom); 132 | emit LossesCorrectionUpdated(to, lossesCorrectionTo); 133 | } 134 | 135 | /** 136 | @dev Mints tokens to an account. Updates lossesCorrection to keep losses unchanged. 137 | @dev It emits a `LossesCorrectionUpdated` event. 138 | @param account The account that will receive the created tokens. 139 | @param value The amount that will be created. 140 | */ 141 | function _mint(address account, uint256 value) internal virtual override { 142 | super._mint(account, value); 143 | 144 | int256 _lossesCorrection = lossesCorrection[account].sub( 145 | (lossesPerShare.mul(value)).toInt256Safe() 146 | ); 147 | 148 | lossesCorrection[account] = _lossesCorrection; 149 | 150 | emit LossesCorrectionUpdated(account, _lossesCorrection); 151 | } 152 | 153 | /** 154 | @dev Burns an amount of the token of a given account. Updates lossesCorrection to keep losses unchanged. 155 | @dev It emits a `LossesCorrectionUpdated` event. 156 | @param account The account from which tokens will be burnt. 157 | @param value The amount that will be burnt. 158 | */ 159 | function _burn(address account, uint256 value) internal virtual override { 160 | super._burn(account, value); 161 | 162 | int256 _lossesCorrection = lossesCorrection[account].add( 163 | (lossesPerShare.mul(value)).toInt256Safe() 164 | ); 165 | 166 | lossesCorrection[account] = _lossesCorrection; 167 | 168 | emit LossesCorrectionUpdated(account, _lossesCorrection); 169 | } 170 | 171 | /** 172 | @dev Registers a loss. May be called directly after a shortfall after BPT burning occurs. 173 | @dev Calls _updateLossesTokenBalance(), whereby the contract computes the delta of the new and previous 174 | losses balance and increments the total losses (cumulative), by delta, by calling _distributeLosses(). 175 | */ 176 | function updateLossesReceived() public virtual { 177 | int256 newLosses = _updateLossesBalance(); 178 | 179 | if (newLosses <= 0) return; 180 | 181 | _distributeLosses(newLosses.toUint256Safe()); 182 | } 183 | 184 | /** 185 | @dev Recognizes all recognizable losses for an account using loss accounting. 186 | */ 187 | function _recognizeLosses() internal virtual returns (uint256 losses) { } 188 | 189 | /** 190 | @dev Updates the current losses balance and returns the difference of the new and previous losses balance. 191 | @return A int256 representing the difference of the new and previous losses balance. 192 | */ 193 | function _updateLossesBalance() internal virtual returns (int256) { } 194 | } 195 | -------------------------------------------------------------------------------- /contracts/token/LoanFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol"; 5 | 6 | import "./BasicFDT.sol"; 7 | 8 | /// @title LoanFDT inherits BasicFDT and uses the original ERC-2222 logic. 9 | abstract contract LoanFDT is BasicFDT { 10 | using SafeMath for uint256; 11 | using SafeMathUint for uint256; 12 | using SignedSafeMath for int256; 13 | using SafeMathInt for int256; 14 | using SafeERC20 for IERC20; 15 | 16 | IERC20 public immutable fundsToken; // The `fundsToken` (dividends). 17 | 18 | uint256 public fundsTokenBalance; // The amount of `fundsToken` (Liquidity Asset) currently present and accounted for in this contract. 19 | 20 | constructor(string memory name, string memory symbol, address _fundsToken) BasicFDT(name, symbol) public { 21 | fundsToken = IERC20(_fundsToken); 22 | } 23 | 24 | /** 25 | @dev Withdraws all available funds for a token holder. 26 | */ 27 | function withdrawFunds() public virtual override { 28 | uint256 withdrawableFunds = _prepareWithdraw(); 29 | 30 | if (withdrawableFunds > uint256(0)) { 31 | fundsToken.safeTransfer(msg.sender, withdrawableFunds); 32 | 33 | _updateFundsTokenBalance(); 34 | } 35 | } 36 | 37 | /** 38 | @dev Updates the current `fundsToken` balance and returns the difference of the new and previous `fundsToken` balance. 39 | @return A int256 representing the difference of the new and previous `fundsToken` balance. 40 | */ 41 | function _updateFundsTokenBalance() internal virtual override returns (int256) { 42 | uint256 _prevFundsTokenBalance = fundsTokenBalance; 43 | 44 | fundsTokenBalance = fundsToken.balanceOf(address(this)); 45 | 46 | return int256(fundsTokenBalance).sub(int256(_prevFundsTokenBalance)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/token/PoolFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./ExtendedFDT.sol"; 5 | 6 | /// @title PoolFDT inherits ExtendedFDT and accounts for gains/losses for Liquidity Providers. 7 | abstract contract PoolFDT is ExtendedFDT { 8 | using SafeMath for uint256; 9 | using SafeMathUint for uint256; 10 | using SignedSafeMath for int256; 11 | using SafeMathInt for int256; 12 | 13 | uint256 public interestSum; // Sum of all withdrawable interest. 14 | uint256 public poolLosses; // Sum of all unrecognized losses. 15 | 16 | uint256 public interestBalance; // The amount of earned interest present and accounted for in this contract. 17 | uint256 public lossesBalance; // The amount of losses present and accounted for in this contract. 18 | 19 | constructor(string memory name, string memory symbol) ExtendedFDT(name, symbol) public { } 20 | 21 | /** 22 | @dev Realizes losses incurred to LPs. 23 | */ 24 | function _recognizeLosses() internal override returns (uint256 losses) { 25 | losses = _prepareLossesWithdraw(); 26 | 27 | poolLosses = poolLosses.sub(losses); 28 | 29 | _updateLossesBalance(); 30 | } 31 | 32 | /** 33 | @dev Updates the current losses balance and returns the difference of the new and previous losses balance. 34 | @return A int256 representing the difference of the new and previous losses balance. 35 | */ 36 | function _updateLossesBalance() internal override returns (int256) { 37 | uint256 _prevLossesTokenBalance = lossesBalance; 38 | 39 | lossesBalance = poolLosses; 40 | 41 | return int256(lossesBalance).sub(int256(_prevLossesTokenBalance)); 42 | } 43 | 44 | /** 45 | @dev Updates the current interest balance and returns the difference of the new and previous interest balance. 46 | @return A int256 representing the difference of the new and previous interest balance. 47 | */ 48 | function _updateFundsTokenBalance() internal override returns (int256) { 49 | uint256 _prevFundsTokenBalance = interestBalance; 50 | 51 | interestBalance = interestSum; 52 | 53 | return int256(interestBalance).sub(int256(_prevFundsTokenBalance)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/token/StakeLockerFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./ExtendedFDT.sol"; 5 | 6 | /// @title StakeLockerFDT inherits ExtendedFDT and accounts for gains/losses for Stakers. 7 | abstract contract StakeLockerFDT is ExtendedFDT { 8 | using SafeMath for uint256; 9 | using SafeMathUint for uint256; 10 | using SignedSafeMath for int256; 11 | using SafeMathInt for int256; 12 | 13 | IERC20 public immutable fundsToken; 14 | 15 | uint256 public bptLosses; // Sum of all unrecognized losses. 16 | uint256 public lossesBalance; // The amount of losses present and accounted for in this contract. 17 | uint256 public fundsTokenBalance; // The amount of `fundsToken` (Liquidity Asset) currently present and accounted for in this contract. 18 | 19 | constructor(string memory name, string memory symbol, address _fundsToken) ExtendedFDT(name, symbol) public { 20 | fundsToken = IERC20(_fundsToken); 21 | } 22 | 23 | /** 24 | @dev Updates loss accounting for `msg.sender`, recognizing losses. 25 | @return losses Amount to be subtracted from a withdraw amount. 26 | */ 27 | function _recognizeLosses() internal override returns (uint256 losses) { 28 | losses = _prepareLossesWithdraw(); 29 | 30 | bptLosses = bptLosses.sub(losses); 31 | 32 | _updateLossesBalance(); 33 | } 34 | 35 | /** 36 | @dev Updates the current losses balance and returns the difference of the new and previous losses balance. 37 | @return A int256 representing the difference of the new and previous losses balance. 38 | */ 39 | function _updateLossesBalance() internal override returns (int256) { 40 | uint256 _prevLossesTokenBalance = lossesBalance; 41 | 42 | lossesBalance = bptLosses; 43 | 44 | return int256(lossesBalance).sub(int256(_prevLossesTokenBalance)); 45 | } 46 | 47 | /** 48 | @dev Updates the current interest balance and returns the difference of the new and previous interest balance. 49 | @return A int256 representing the difference of the new and previous interest balance. 50 | */ 51 | function _updateFundsTokenBalance() internal virtual override returns (int256) { 52 | uint256 _prevFundsTokenBalance = fundsTokenBalance; 53 | 54 | fundsTokenBalance = fundsToken.balanceOf(address(this)); 55 | 56 | return int256(fundsTokenBalance).sub(int256(_prevFundsTokenBalance)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/token/interfaces/IBaseFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | interface IBaseFDT { 5 | 6 | /** 7 | @dev Returns the total amount of funds a given address is able to withdraw currently. 8 | @param owner Address of FDT holder. 9 | @return A uint256 representing the available funds for a given account. 10 | */ 11 | function withdrawableFundsOf(address owner) external view returns (uint256); 12 | 13 | /** 14 | @dev Withdraws all available funds for a FDT holder. 15 | */ 16 | function withdrawFunds() external; 17 | 18 | /** 19 | @dev This event emits when new funds are distributed. 20 | @param by The address of the sender that distributed funds. 21 | @param fundsDistributed The amount of funds received for distribution. 22 | */ 23 | event FundsDistributed(address indexed by, uint256 fundsDistributed); 24 | 25 | /** 26 | @dev This event emits when distributed funds are withdrawn by a token holder. 27 | @param by The address of the receiver of funds. 28 | @param fundsWithdrawn The amount of funds that were withdrawn. 29 | @param totalWithdrawn The total amount of funds that were withdrawn. 30 | */ 31 | event FundsWithdrawn(address indexed by, uint256 fundsWithdrawn, uint256 totalWithdrawn); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /contracts/token/interfaces/IBasicFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 5 | 6 | import "./IBaseFDT.sol"; 7 | 8 | interface IBasicFDT is IBaseFDT, IERC20 { 9 | 10 | event PointsPerShareUpdated(uint256); 11 | 12 | event PointsCorrectionUpdated(address indexed, int256); 13 | 14 | function withdrawnFundsOf(address) external view returns (uint256); 15 | 16 | function accumulativeFundsOf(address) external view returns (uint256); 17 | 18 | function updateFundsReceived() external; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /contracts/token/interfaces/IExtendedFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./IBasicFDT.sol"; 5 | 6 | interface IExtendedFDT is IBasicFDT { 7 | 8 | event LossesPerShareUpdated(uint256); 9 | 10 | event LossesCorrectionUpdated(address indexed, int256); 11 | 12 | event LossesDistributed(address indexed, uint256); 13 | 14 | event LossesRecognized(address indexed, uint256, uint256); 15 | 16 | function lossesPerShare() external view returns (uint256); 17 | 18 | function recognizableLossesOf(address) external view returns (uint256); 19 | 20 | function recognizedLossesOf(address) external view returns (uint256); 21 | 22 | function accumulativeLossesOf(address) external view returns (uint256); 23 | 24 | function updateLossesReceived() external; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /contracts/token/interfaces/ILoanFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./IBasicFDT.sol"; 5 | 6 | interface ILoanFDT is IBasicFDT { 7 | 8 | function fundsToken() external view returns (address); 9 | 10 | function fundsTokenBalance() external view returns (uint256); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /contracts/token/interfaces/IPoolFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./IExtendedFDT.sol"; 5 | 6 | interface IPoolFDT is IExtendedFDT { 7 | 8 | function interestSum() external view returns (uint256); 9 | 10 | function poolLosses() external view returns (uint256); 11 | 12 | function interestBalance() external view returns (uint256); 13 | 14 | function lossesBalance() external view returns (uint256); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /contracts/token/interfaces/IStakeLockerFDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.6.11; 3 | 4 | import "./IExtendedFDT.sol"; 5 | 6 | interface IStakeLockerFDT is IExtendedFDT { 7 | 8 | function fundsToken() external view returns (address); 9 | 10 | function fundsTokenBalance() external view returns (uint256); 11 | 12 | function bptLosses() external view returns (uint256); 13 | 14 | function lossesBalance() external view returns (uint256); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /flatten.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | mkdir -p flattened-contracts/functions 5 | 6 | FILES1=./contracts/*.sol 7 | FILES2=./contracts/library/*.sol 8 | FILES3=./contracts/oracles/*.sol 9 | 10 | for f in $FILES1 11 | do 12 | filepath=${f:2} 13 | filename=${filepath:10} 14 | echo "Flattening: $filename" 15 | hevm flatten --source-file $filepath | sed '/SPDX-License-Identifier/d' | sed -e '1s/^/\/\/ SPDX-License-Identifier: AGPL-3.0-or-later /' | sed -r 'N;s/(\/\/\/\/\/\/ .*)\n\s*$/\1/;P;D' > "flattened-contracts/$filename" 16 | cat "flattened-contracts/$filename" | grep -E ") external |) public" > "flattened-contracts/functions/$filename" 17 | done 18 | 19 | for f in $FILES2 20 | do 21 | filepath=${f:2} 22 | filename=${filepath:17} 23 | echo "Flattening: $filename" 24 | hevm flatten --source-file $filepath | sed '/SPDX-License-Identifier/d' | sed -e '1s/^/\/\/ SPDX-License-Identifier: AGPL-3.0-or-later /' | sed -r 'N;s/(\/\/\/\/\/\/ .*)\n\s*$/\1/;P;D' > "flattened-contracts/$filename" 25 | done 26 | 27 | for f in $FILES3 28 | do 29 | filepath=${f:2} 30 | filename=${filepath:17} 31 | echo "Flattening: $filename" 32 | hevm flatten --source-file $filepath | sed '/SPDX-License-Identifier/d' | sed -e '1s/^/\/\/ SPDX-License-Identifier: AGPL-3.0-or-later /' | sed -r 'N;s/(\/\/\/\/\/\/ .*)\n\s*$/\1/;P;D' > "flattened-contracts/$filename" 33 | done 34 | -------------------------------------------------------------------------------- /flattened-contracts/IChainlinkAggregatorV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later // hevm: flattened sources of contracts/oracles/IChainlinkAggregatorV3.sol 2 | pragma solidity =0.6.11; 3 | 4 | ////// contracts/oracles/IChainlinkAggregatorV3.sol 5 | /* pragma solidity 0.6.11; */ 6 | 7 | interface IChainlinkAggregatorV3 { 8 | 9 | function decimals() external view returns (uint8); 10 | function description() external view returns (string memory); 11 | function version() external view returns (uint256); 12 | 13 | // getRoundData and latestRoundData should both raise "No data present" 14 | // if they do not have data to report, instead of returning unset values, 15 | // which could be misinterpreted as actual reported values. 16 | 17 | function getRoundData(uint80 _roundId) 18 | external 19 | view 20 | returns ( 21 | uint80 roundId, 22 | int256 answer, 23 | uint256 startedAt, 24 | uint256 updatedAt, 25 | uint80 answeredInRound 26 | ); 27 | 28 | function latestRoundData() 29 | external 30 | view 31 | returns ( 32 | uint80 roundId, 33 | int256 answer, 34 | uint256 startedAt, 35 | uint256 updatedAt, 36 | uint80 answeredInRound 37 | ); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /flattened-contracts/UsdOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later // hevm: flattened sources of contracts/oracles/UsdOracle.sol 2 | pragma solidity =0.6.11; 3 | 4 | ////// contracts/oracles/UsdOracle.sol 5 | /* pragma solidity 0.6.11; */ 6 | 7 | /// @title UsdOracle is a constant price oracle feed that always returns 1 USD in 8 decimal precision. 8 | contract UsdOracle { 9 | 10 | int256 constant USD_PRICE = 1 * 10 ** 8; 11 | 12 | /** 13 | @dev Returns the constant USD price. 14 | */ 15 | function getLatestPrice() public pure returns (int256) { 16 | return USD_PRICE; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test-ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | [[ "$ETH_RPC_URL" && "$(seth chain)" == "ethlive" ]] || { echo "Please set a mainnet ETH_RPC_URL"; exit 1; } 5 | 6 | export DAPP_TEST_TIMESTAMP=1612311576 7 | export DAPP_TEST_NUMBER=11780000 8 | export DAPP_SKIP_BUILD=1 9 | export DAPP_SOLC_VERSION=0.6.11 10 | export DAPP_SRC="contracts" 11 | export SOLC_FLAGS="--optimize --optimize-runs 200" 12 | export DAPP_LINK_TEST_LIBRARIES=1 13 | 14 | LANG=C.UTF-8 dapp test --match ${1} --rpc-url "$ETH_RPC_URL" --verbose --cache "cache/dapp-cache" --fuzz-runs 1 15 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | while getopts t:r:b: flag 5 | do 6 | case "${flag}" in 7 | t) test=${OPTARG};; 8 | r) runs=${OPTARG};; 9 | b) build=${OPTARG};; 10 | esac 11 | done 12 | 13 | runs=$([ -z "$runs" ] && echo "10" || echo "$runs") 14 | build=$([ -z "$build" ] && echo "1" || echo "$build") 15 | skip_build=$([ "$build" == "0" ] && echo "1" || echo "0") 16 | 17 | [[ $SKIP_MAINNET_CHECK || "$ETH_RPC_URL" && "$(seth chain)" == "ethlive" ]] || { echo "Please set a mainnet ETH_RPC_URL"; exit 1; } 18 | 19 | export DAPP_TEST_TIMESTAMP=1615792486 20 | export DAPP_TEST_NUMBER=12045000 21 | export DAPP_SOLC_VERSION=0.6.11 22 | export DAPP_SRC="contracts" 23 | export DAPP_BUILD_OPTIMIZE=1 24 | export DAPP_BUILD_OPTIMIZE_RUNS=200 25 | export DAPP_LINK_TEST_LIBRARIES=1 26 | 27 | if [ "$skip_build" = "1" ]; then export DAPP_SKIP_BUILD=1; fi 28 | 29 | if [ -z "$test" ]; then match="contracts/test"; dapp_test_verbosity=1; else match=$test; dapp_test_verbosity=2; fi 30 | 31 | LANG=C.UTF-8 dapp test --match "$match" --rpc-url "$ETH_RPC_URL" --verbosity $dapp_test_verbosity --cache "cache/dapp-cache" --fuzz-runs $runs 32 | --------------------------------------------------------------------------------