diff --git a/.github/workflows/forge-pr.yaml b/.github/workflows/forge-pr.yaml index 478c6fc..6a7027f 100644 --- a/.github/workflows/forge-pr.yaml +++ b/.github/workflows/forge-pr.yaml @@ -3,7 +3,8 @@ name: Forge Tests (PR) on: [pull_request] jobs: - run-ci: + test: + name: Test with "deep" profile runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -39,11 +40,32 @@ jobs: - name: Generate coverage report run: | forge coverage --report lcov + sudo apt-get install lcov + lcov --remove lcov.info -o lcov.info 'tests/*' - name: Report code coverage uses: zgosalvez/github-actions-report-lcov@v1 with: coverage-files: lcov.info - minimum-coverage: 65 + minimum-coverage: 90 artifact-name: code-coverage-report github-token: ${{ secrets.GITHUB_TOKEN }} working-directory: ./ + + size_check: + name: Check contracts sizes + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Install submodules + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --recursive + + - name: Check contract sizes + run: ./scripts/check-sizes.sh diff --git a/.github/workflows/forge.yml b/.github/workflows/forge.yml index 2d729df..0a370cd 100644 --- a/.github/workflows/forge.yml +++ b/.github/workflows/forge.yml @@ -6,7 +6,8 @@ on: - main jobs: - run-ci: + test: + name: Test with "deep" profile runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -42,11 +43,32 @@ jobs: - name: Generate coverage report run: | forge coverage --report lcov + sudo apt-get install lcov + lcov --remove lcov.info -o lcov.info 'tests/*' - name: Report code coverage uses: zgosalvez/github-actions-report-lcov@v1 with: coverage-files: lcov.info - minimum-coverage: 65 + minimum-coverage: 90 artifact-name: code-coverage-report github-token: ${{ secrets.GITHUB_TOKEN }} working-directory: ./ + + size_check: + name: Check contracts sizes + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Install submodules + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --recursive + + - name: Check contract sizes + run: ./scripts/check-sizes.sh diff --git a/.github/workflows/slither.yml b/.github/workflows/slither.yml deleted file mode 100644 index 97e4089..0000000 --- a/.github/workflows/slither.yml +++ /dev/null @@ -1,54 +0,0 @@ -# name: Static Analysis -# on: -# push: -# branches: "*" - -# jobs: -# build: -# runs-on: ubuntu-latest - -# steps: -# - uses: actions/checkout@v2 - -# - name: Set up Python ${{ matrix.python-version }} -# uses: actions/setup-python@v2 -# with: -# python-version: 3.8 - -# - name: Install dependencies -# run: | -# sudo snap install solc -# python -m pip install --upgrade pip -# pip install slither-analyzer==0.8.2 solc-select==0.2.1 -# solc-select install 0.8.7 -# solc-select use 0.8.7 - -# - name: Install submodules -# run: | -# git config --global url."https://github.com/".insteadOf "git@github.com:" -# git submodule update --init --recursive - -# - name: Summary of static analysis -# run: | -# slither contracts --print human-summary - -# - name: Contract summary of static analysis -# run: | -# slither contracts --print contract-summary - -# - name: Function summary -# run: | -# slither contracts --print function-summary - -# - name: Inheritance -# run: | -# slither contracts --print inheritance - -# - name: Data dependency -# run: | -# slither contracts --print data-dependency - -# - name: Static Analysis -# run: | -# slither contracts -# continue-on-error: true diff --git a/Makefile b/Makefile index 71146e4..bf5f435 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,11 @@ -install: - @git submodule update --init --recursive - -update: - @forge update - build: - @scripts/build.sh -p default + @scripts/build.sh -p production release: @scripts/release.sh +size: + @scripts/check-sizes.sh + test: @scripts/test.sh -p default - -clean: - @forge clean diff --git a/README.md b/README.md index 3f47c66..581e1f5 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ forge install | v3.0.1 - v4.0.0 | Three Sigma | [`Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223541/three-sigma_maple-finance_code-audit_v1.1.1.pdf) | 2022-10-24 | | v5.0.0 - v5.0.1 | Spearbit Auditors via Cantina | [`2023-06-05 - Cantina Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/11667848/cantina-maple.pdf) | 2023-06-05 | | v5.0.0 - v5.0.1 | Three Sigma | [`2023-04-10 - Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/11663546/maple-v2-audit_three-sigma_2023.pdf) | 2023-04-21 | +| Three Sigma | [`2023-11-06 - Three Sigma Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/13707288/Maple-Q4-Three-Sigma-Audit.pdf) | +| 0xMacro | [`2023-11-27 - 0xMacro Report`](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/13707291/Maple-Q4-0xMacro-Audit.pdf) | ## Bug Bounty diff --git a/configs/package.yaml b/configs/package.yaml index 4696581..70a7179 100644 --- a/configs/package.yaml +++ b/configs/package.yaml @@ -1,5 +1,5 @@ name: fixed-term-loan -version: 5.0.1 +version: 5.0.2 source: contracts packages: - path: contracts/MapleLoan.sol @@ -10,8 +10,6 @@ packages: contractName: MapleLoanFeeManager - path: contracts/MapleLoanInitializer.sol contractName: MapleLoanInitializer - - path: contracts/MapleLoanStorage.sol - contractName: MapleLoanStorage - path: contracts/MapleLoanV5Migrator.sol contractName: MapleLoanV5Migrator - path: contracts/Refinancer.sol diff --git a/contracts/MapleLoanFactory.sol b/contracts/MapleLoanFactory.sol index da8189a..4c1f0a0 100644 --- a/contracts/MapleLoanFactory.sol +++ b/contracts/MapleLoanFactory.sol @@ -4,21 +4,31 @@ pragma solidity 0.8.7; import { IMapleProxyFactory, MapleProxyFactory } from "../modules/maple-proxy-factory/contracts/MapleProxyFactory.sol"; import { IMapleLoanFactory } from "./interfaces/IMapleLoanFactory.sol"; +import { IGlobalsLike } from "./interfaces/Interfaces.sol"; /// @title MapleLoanFactory deploys Loan instances. contract MapleLoanFactory is IMapleLoanFactory, MapleProxyFactory { - mapping(address => bool) public override isLoan; + address public immutable override oldFactory; - /// @param mapleGlobals_ The address of a Maple Globals contract. - constructor(address mapleGlobals_) MapleProxyFactory(mapleGlobals_) {} + mapping(address => bool) internal _isLoan; + + constructor(address mapleGlobals_, address oldFactory_) MapleProxyFactory(mapleGlobals_) { + oldFactory = oldFactory_; + } function createInstance(bytes calldata arguments_, bytes32 salt_) override(IMapleProxyFactory, MapleProxyFactory) public returns ( address instance_ ) { - isLoan[instance_ = super.createInstance(arguments_, salt_)] = true; + require(IGlobalsLike(mapleGlobals).canDeploy(msg.sender), "MLF:CI:CANNOT_DEPLOY"); + + _isLoan[instance_ = super.createInstance(arguments_, salt_)] = true; + } + + function isLoan(address instance_) external view override returns (bool) { + return (_isLoan[instance_] || IMapleLoanFactory(oldFactory).isLoan(instance_)); } } diff --git a/contracts/MapleLoanFeeManager.sol b/contracts/MapleLoanFeeManager.sol index 2d6cd98..04ec698 100644 --- a/contracts/MapleLoanFeeManager.sol +++ b/contracts/MapleLoanFeeManager.sol @@ -21,6 +21,7 @@ import { IMapleLoanFeeManager } from "./interfaces/IMapleLoanFeeManager.sol"; ██║ ╚═╝ ██║██║ ██║██║ ███████╗███████╗ ███████╗╚██████╔╝██║ ██║██║ ╚████║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ + ███████╗███████╗███████╗ ███╗ ███╗ █████╗ ███╗ ██╗ █████╗ ██████╗ ███████╗██████╗ ██╔════╝██╔════╝██╔════╝ ████╗ ████║██╔══██╗████╗ ██║██╔══██╗██╔════╝ ██╔════╝██╔══██╗ █████╗ █████╗ █████╗ ██╔████╔██║███████║██╔██╗ ██║███████║██║ ███╗█████╗ ██████╔╝ diff --git a/contracts/MapleLoanV502Migrator.sol b/contracts/MapleLoanV502Migrator.sol new file mode 100644 index 0000000..af8600f --- /dev/null +++ b/contracts/MapleLoanV502Migrator.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.7; + +import { ProxiedInternals } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/ProxiedInternals.sol"; + +import { IGlobalsLike, IMapleProxyFactoryLike } from "./interfaces/Interfaces.sol"; + +import { MapleLoanStorage } from "./MapleLoanStorage.sol"; + +/// @title MapleLoanV502Migrator is to update the factory address for each deployed loan. +contract MapleLoanV502Migrator is ProxiedInternals, MapleLoanStorage { + + fallback() external { + ( address newFactory_ ) = abi.decode(msg.data, (address)); + + require(_factory() != newFactory_, "MLV502M:INVALID_NO_OP"); + + address globals = IMapleProxyFactoryLike(_factory()).mapleGlobals(); + + require(IGlobalsLike(globals).isInstanceOf("FT_LOAN_FACTORY", newFactory_), "MLV502M:INVALID_FACTORY"); + + _setFactory(newFactory_); + } + +} diff --git a/contracts/Refinancer.sol b/contracts/MapleRefinancer.sol similarity index 58% rename from contracts/Refinancer.sol rename to contracts/MapleRefinancer.sol index 49f70f9..e24221d 100644 --- a/contracts/Refinancer.sol +++ b/contracts/MapleRefinancer.sol @@ -4,23 +4,23 @@ pragma solidity 0.8.7; import { IERC20 } from "../modules/erc20/contracts/interfaces/IERC20.sol"; import { IMapleLoanFeeManager } from "./interfaces/IMapleLoanFeeManager.sol"; -import { IRefinancer } from "./interfaces/IRefinancer.sol"; +import { IMapleRefinancer } from "./interfaces/IMapleRefinancer.sol"; import { MapleLoanStorage } from "./MapleLoanStorage.sol"; /* - ██████╗ ███████╗███████╗██╗███╗ ██╗ █████╗ ███╗ ██╗ ██████╗███████╗██████╗ - ██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔══██╗████╗ ██║██╔════╝██╔════╝██╔══██╗ - ██████╔╝█████╗ █████╗ ██║██╔██╗ ██║███████║██╔██╗ ██║██║ █████╗ ██████╔╝ - ██╔══██╗██╔══╝ ██╔══╝ ██║██║╚██╗██║██╔══██║██║╚██╗██║██║ ██╔══╝ ██╔══██╗ - ██║ ██║███████╗██║ ██║██║ ╚████║██║ ██║██║ ╚████║╚██████╗███████╗██║ ██║ - ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝ ╚═╝ + ███╗ ███╗ █████╗ ██████╗ ██╗ ███████╗ ██████╗ ███████╗███████╗██╗███╗ ██╗ █████╗ ███╗ ██╗ ██████╗███████╗██████╗ + ████╗ ████║██╔══██╗██╔══██╗██║ ██╔════╝ ██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔══██╗████╗ ██║██╔════╝██╔════╝██╔══██╗ + ██╔████╔██║███████║██████╔╝██║ █████╗ ██████╔╝█████╗ █████╗ ██║██╔██╗ ██║███████║██╔██╗ ██║██║ █████╗ ██████╔╝ + ██║╚██╔╝██║██╔══██║██╔═══╝ ██║ ██╔══╝ ██╔══██╗██╔══╝ ██╔══╝ ██║██║╚██╗██║██╔══██║██║╚██╗██║██║ ██╔══╝ ██╔══██╗ + ██║ ╚═╝ ██║██║ ██║██║ ███████╗███████╗ ██║ ██║███████╗██║ ██║██║ ╚████║██║ ██║██║ ╚████║╚██████╗███████╗██║ ██║ + ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝ ╚═╝ */ -/// @title Refinancer uses storage from a MapleLoan defined by MapleLoanStorage. -contract Refinancer is IRefinancer, MapleLoanStorage { +/// @title MapleRefinancer uses storage from a MapleLoan defined by MapleLoanStorage. +contract MapleRefinancer is IMapleRefinancer, MapleLoanStorage { function increasePrincipal(uint256 amount_) external override { // Cannot under-fund the principal increase, but over-funding results in additional funds left unaccounted for. diff --git a/contracts/interfaces/IMapleLoanFactory.sol b/contracts/interfaces/IMapleLoanFactory.sol index 2c5c8d3..5e617d8 100644 --- a/contracts/interfaces/IMapleLoanFactory.sol +++ b/contracts/interfaces/IMapleLoanFactory.sol @@ -7,10 +7,16 @@ import { IMapleProxyFactory } from "../../modules/maple-proxy-factory/contracts/ interface IMapleLoanFactory is IMapleProxyFactory { /** - * @dev Whether the proxy is a MapleLoan deployed by this factory. + * @dev Whether the proxy is a MapleLoan deployed by this factory or the old factory. * @param proxy_ The address of the proxy contract. * @return isLoan_ Whether the proxy is a MapleLoan deployed by this factory. */ function isLoan(address proxy_) external view returns (bool isLoan_); + /** + * @dev Returns the address of the old factory. + * @return oldFactory_ The address of the old factory. + */ + function oldFactory() external view returns (address oldFactory_); + } diff --git a/contracts/interfaces/IRefinancer.sol b/contracts/interfaces/IMapleRefinancer.sol similarity index 98% rename from contracts/interfaces/IRefinancer.sol rename to contracts/interfaces/IMapleRefinancer.sol index 09b3739..b625361 100644 --- a/contracts/interfaces/IRefinancer.sol +++ b/contracts/interfaces/IMapleRefinancer.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.7; -/// @title Refinancer uses storage from Maple Loan. -interface IRefinancer { +/// @title MapleRefinancer uses storage from Maple Loan. +interface IMapleRefinancer { /**************************************************************************************************************************************/ /*** Events ***/ diff --git a/contracts/interfaces/Interfaces.sol b/contracts/interfaces/Interfaces.sol index 3c3d829..bfc0545 100644 --- a/contracts/interfaces/Interfaces.sol +++ b/contracts/interfaces/Interfaces.sol @@ -23,6 +23,8 @@ interface IGlobalsLike { function securityAdmin() external view returns (address securityAdmin_); + function canDeploy(address caller_) external view returns (bool canDeploy_); + } interface ILenderLike { diff --git a/foundry.toml b/foundry.toml index 0e4272d..05293fe 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,10 +3,10 @@ contracts = 'contracts' # The contract directory test = 'tests' # The test directory libs = ['modules'] # A list of library directories solc_version = '0.8.7' # Override for the solc version (setting this ignores `auto_detect_solc`) -optimizer = true # Enable or disable the solc optimizer -optimizer_runs = 200 # The number of optimizer runs +optimizer = false # Enable or disable the solc optimizer verbosity = 3 # The verbosity of tests block_timestamp = 1_622_400_000 # Timestamp for tests (non-zero) +fuzz_runs = 100 # Number of fuzz runs [profile.deep] fuzz_runs = 1000 @@ -14,6 +14,6 @@ fuzz_runs = 1000 [profile.super_deep] fuzz_runs = 50000 -[profile.release] -optimizer = true # Enable or disable the solc optimizer -optimizer_runs = 200 # The number of optimizer runs +[profile.production] +optimizer = true # Enable or disable the solc optimizer +optimizer_runs = 200 # The number of optimizer runs diff --git a/scripts/check-sizes.sh b/scripts/check-sizes.sh new file mode 100755 index 0000000..0ba6bc1 --- /dev/null +++ b/scripts/check-sizes.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +while getopts p: flag +do + case "${flag}" in + p) profile=${OPTARG};; + esac +done + +export FOUNDRY_PROFILE=production + +sizes=$(forge build --sizes) + +names=($(cat ./configs/package.yaml | grep " contractName:" | sed -r 's/.{18}//')) + +fail=false + +for i in "${!names[@]}"; do + line=$(echo "$sizes" | grep -w "${names[i]}") + + if [[ $line == *"-"* ]]; then + echo "${names[i]} is too large" + fail=true + fi +done + +if $fail + then + echo "Contract size check failed" + exit 1 + else + echo "Contract size check passed" +fi diff --git a/scripts/release.sh b/scripts/release.sh index 8529992..09b7475 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -5,7 +5,7 @@ version=$(cat ./configs/package.yaml | grep "version: " | sed -r 's/.{9}//') name=$(cat ./configs/package.yaml | grep "name: " | sed -r 's/.{6}//') customDescription=$(cat ./configs/package.yaml | grep "customDescription: " | sed -r 's/.{19}//') -./scripts/build.sh -p release +./scripts/build.sh -p production rm -rf ./package mkdir -p package diff --git a/tests/InitializerAndMigrator.t.sol b/tests/InitializerAndMigrator.t.sol index b4d933c..ceedb61 100644 --- a/tests/InitializerAndMigrator.t.sol +++ b/tests/InitializerAndMigrator.t.sol @@ -38,7 +38,7 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { lender = new MockLoanManager(); migrator = new MapleLoanV5Migrator(); - factory = new MapleLoanFactory(address(globals)); + factory = new MapleLoanFactory(address(globals), address(0)); loanManagerFactory = MockLoanManagerFactory(lender.factory()); lender.__setFundsAsset(address(asset)); @@ -47,6 +47,7 @@ contract MapleLoanInitializerAndMigratorTests is TestUtils { globals.setValidCollateralAsset(address(asset), true); globals.setValidPoolAsset(address(asset), true); globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); globals.__setSecurityAdmin(securityAdmin); vm.startPrank(governor); diff --git a/tests/MapleLoanFactory.t.sol b/tests/MapleLoanFactory.t.sol index 67e1dee..7031c17 100644 --- a/tests/MapleLoanFactory.t.sol +++ b/tests/MapleLoanFactory.t.sol @@ -7,19 +7,21 @@ import { MapleLoan } from "../contracts/MapleLoan.sol"; import { MapleLoanFactory } from "../contracts/MapleLoanFactory.sol"; import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; -import { MockFeeManager, MockGlobals, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; +import { MockFeeManager, MockGlobals, MockLoanManager, MockLoanManagerFactory, MockLoanFactory } from "./mocks/Mocks.sol"; import { Proxy } from "../modules/maple-proxy-factory/modules/proxy-factory/contracts/Proxy.sol"; contract MapleLoanFactoryTest is TestUtils { + MockLoanFactory internal oldFactory; MapleLoanFactory internal factory; MockFeeManager internal feeManager; MockGlobals internal globals; MockLoanManager internal lender; MockLoanManagerFactory internal loanManagerFactory; - address internal governor = address(new Address()); + address internal governor = address(new Address()); + address internal securityAdmin = address(new Address()); address internal implementation; address internal initializer; @@ -32,7 +34,10 @@ contract MapleLoanFactoryTest is TestUtils { implementation = address(new MapleLoan()); initializer = address(new MapleLoanInitializer()); - factory = new MapleLoanFactory(address(globals)); + oldFactory = new MockLoanFactory(); + factory = new MapleLoanFactory(address(globals), address(oldFactory)); + + lender.__setFundsAsset(address(1)); lender.__setFundsAsset(address(1)); @@ -41,6 +46,7 @@ contract MapleLoanFactoryTest is TestUtils { globals.setValidPoolAsset(address(1), true); globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); vm.startPrank(governor); factory.registerImplementation(1, implementation, initializer); @@ -145,7 +151,7 @@ contract MapleLoanFactoryTest is TestUtils { uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; uint256[2] memory fees = [uint256(0), uint256(0)]; - + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( address(1), address(lender), @@ -249,6 +255,34 @@ contract MapleLoanFactoryTest is TestUtils { factory.createInstance(arguments, salt); } + function test_createInstance_invalidCaller() external { + address[2] memory assets = [address(1), address(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; + uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; + uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + globals.__setCanDeploy(false); + + vm.expectRevert("MLF:CI:CANNOT_DEPLOY"); + factory.createInstance(arguments, "SALT"); + + globals.__setCanDeploy(true); + + factory.createInstance(arguments, "SALT"); + } + function test_createInstance(bytes32 salt_) external { address[2] memory assets = [address(1), address(1)]; uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; @@ -281,8 +315,45 @@ contract MapleLoanFactoryTest is TestUtils { // TODO: Change back to hardcoded address once IPFS hashes can be removed on compilation in Foundry. assertEq(loan, expectedAddress); - assertTrue(!factory.isLoan(address(1))); + assertTrue(!oldFactory.isLoan(address(1))); assertTrue( factory.isLoan(loan)); } + function test_isLoan_withOldFactory() external { + address[2] memory assets = [address(1), address(1)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(1), uint256(1)]; + uint256[3] memory amounts = [uint256(1), uint256(1), uint256(0)]; + uint256[4] memory rates = [uint256(0), uint256(0), uint256(0), uint256(0)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + // Assert current factory points to the correct old loan factory + assertEq(factory.oldFactory(), address(oldFactory)); + + address oldLoan = address(new Address()); + + oldFactory.__setIsLoan(oldLoan, true); + + assertTrue(oldFactory.isLoan(oldLoan)); + assertTrue(factory.isLoan(oldLoan)); + + address loan1 = factory.createInstance(arguments, keccak256(abi.encodePacked("salt1"))); + address loan2 = factory.createInstance(arguments, keccak256(abi.encodePacked("salt2"))); + + assertTrue(!oldFactory.isLoan(loan1)); + assertTrue( factory.isLoan(loan1)); + assertTrue( factory.isLoan(loan2)); + assertTrue( factory.isLoan(oldLoan)); + } + } diff --git a/tests/MapleLoanFeeManager.t.sol b/tests/MapleLoanFeeManager.t.sol index 562d82e..4f0e8eb 100644 --- a/tests/MapleLoanFeeManager.t.sol +++ b/tests/MapleLoanFeeManager.t.sol @@ -11,7 +11,7 @@ import { MapleLoanFeeManager } from "../contracts/MapleLoanFeeManager.sol"; import { MockGlobals, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; -contract FeeManagerBase is TestUtils { +contract TestBase is TestUtils { address internal BORROWER = address(new Address()); address internal GOVERNOR = address(new Address()); @@ -48,7 +48,7 @@ contract FeeManagerBase is TestUtils { lender.__setFundsAsset(address(fundsAsset)); - factory = new MapleLoanFactory(address(globals)); + factory = new MapleLoanFactory(address(globals), address(0)); feeManager = new MapleLoanFeeManager(address(globals)); lender.__setPoolManager(address(poolManager)); @@ -59,6 +59,7 @@ contract FeeManagerBase is TestUtils { globals.setMapleTreasury(TREASURY); globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); globals.setValidBorrower(BORROWER, true); globals.setValidCollateralAsset(address(collateralAsset), true); globals.setValidPoolAsset(address(fundsAsset), true); @@ -114,7 +115,7 @@ contract FeeManagerBase is TestUtils { } -contract PayClosingFeesTests is FeeManagerBase { +contract PayClosingFeesTests is TestBase { MapleLoan loan; @@ -164,7 +165,7 @@ contract PayClosingFeesTests is FeeManagerBase { } -contract PayOriginationFeesTests is FeeManagerBase { +contract PayOriginationFeesTests is TestBase { MapleLoan loan; @@ -224,7 +225,7 @@ contract PayOriginationFeesTests is FeeManagerBase { } -contract PayServiceFeesTests is FeeManagerBase { +contract PayServiceFeesTests is TestBase { MapleLoan loan; @@ -291,7 +292,7 @@ contract PayServiceFeesTests is FeeManagerBase { } -contract UpdatePlatformServiceFeeTests is FeeManagerBase { +contract UpdatePlatformServiceFeeTests is TestBase { function test_updatePlatformServiceFee() external { address loan1 = _createLoan( @@ -343,7 +344,7 @@ contract UpdatePlatformServiceFeeTests is FeeManagerBase { } -contract UpdateFeeTerms_SetterTests is FeeManagerBase { +contract UpdateDelegateFeeTermsTests is TestBase { function test_updateDelegateFeeTerms() external { address someContract = address(new Address()); @@ -360,7 +361,7 @@ contract UpdateFeeTerms_SetterTests is FeeManagerBase { } -contract FeeManager_Getters is FeeManagerBase { +contract GetterTests is TestBase { function setUp() public override { super.setUp(); diff --git a/tests/MapleLoanLogic.t.sol b/tests/MapleLoanLogic.t.sol index d20da44..a42acbe 100644 --- a/tests/MapleLoanLogic.t.sol +++ b/tests/MapleLoanLogic.t.sol @@ -8,7 +8,7 @@ import { ConstructableMapleLoan, MapleLoanHarness } from "./harnesses/MapleLoanH import { MockGlobals, MockFactory, MockFeeManager, MockLoanManager, RevertingERC20 } from "./mocks/Mocks.sol"; -import { Refinancer } from "../contracts/Refinancer.sol"; +import { MapleRefinancer } from "../contracts/MapleRefinancer.sol"; contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { @@ -31,7 +31,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { MockFeeManager internal feeManager; MockGlobals internal globals; MockLoanManager internal lender; - Refinancer internal refinancer; + MapleRefinancer internal refinancer; function setUp() external { collateralAsset = new MockERC20("Token0", "T0", 0); @@ -39,7 +39,7 @@ contract MapleLoanLogic_AcceptNewTermsTests is TestUtils { fundsAsset = new MockERC20("Token1", "T1", 0); globals = new MockGlobals(governor); lender = new MockLoanManager(); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); factory = new MockFactory(address(globals)); @@ -2387,14 +2387,16 @@ contract MapleLoanLogic_RejectNewTermsTests is TestUtils { MapleLoanHarness loan; MockFactory factory; MockGlobals globals; - Refinancer refinancer; + MapleRefinancer refinancer; address borrower = address(new Address()); function setUp() external { globals = new MockGlobals(address(0)); loan = new MapleLoanHarness(); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); + + factory = new MockFactory(address(globals)); factory = new MockFactory(address(globals)); @@ -2424,7 +2426,7 @@ contract MapleLoanLogic_RejectNewTermsTests is TestUtils { vm.startPrank(borrower); loan.proposeNewTerms(address(refinancer), deadline, calls); - address anotherRefinancer = address(new Refinancer()); + address anotherRefinancer = address(new MapleRefinancer()); vm.expectRevert("ML:RNT:COMMITMENT_MISMATCH"); loan.rejectNewTerms(anotherRefinancer, deadline, calls); } diff --git a/tests/Refinancer.t.sol b/tests/MapleLoanRefinancer.t.sol similarity index 98% rename from tests/Refinancer.t.sol rename to tests/MapleLoanRefinancer.t.sol index 3fda891..9603f64 100644 --- a/tests/Refinancer.t.sol +++ b/tests/MapleLoanRefinancer.t.sol @@ -5,7 +5,7 @@ import { Address, TestUtils } from "../modules/contract-test-utils/contracts/tes import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockERC20.sol"; import { MapleLoanFeeManager } from "../contracts/MapleLoanFeeManager.sol"; -import { Refinancer } from "../contracts/Refinancer.sol"; +import { MapleRefinancer } from "../contracts/MapleRefinancer.sol"; import { ConstructableMapleLoan } from "./harnesses/MapleLoanHarnesses.sol"; @@ -18,7 +18,7 @@ import { } from "./mocks/Mocks.sol"; // Helper contract with common functionality -contract RefinancerTestBase is TestUtils { +contract TestBase is TestUtils { // Loan Boundaries uint256 internal constant MAX_PAYMENTS = 20; @@ -33,7 +33,7 @@ contract RefinancerTestBase is TestUtils { MockFeeManager internal feeManager; MockGlobals internal globals; MockLoanManager internal lender; - Refinancer internal refinancer; + MapleRefinancer internal refinancer; address internal borrower = address(new Address()); address internal governor = address(new Address()); @@ -42,7 +42,7 @@ contract RefinancerTestBase is TestUtils { feeManager = new MockFeeManager(); globals = new MockGlobals(governor); lender = new MockLoanManager(); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); token = new MockERC20("Test", "TST", 0); factory = new MockFactory(address(globals)); @@ -106,7 +106,115 @@ contract RefinancerTestBase is TestUtils { } -contract RefinancerCollateralRequiredTests is RefinancerTestBase { +contract MapleLoanRefinancerMiscellaneousTests is TestBase { + + function test_refinance_invalidRefinancer() external { + setUpOngoingLoan(1, 1, 1, 12 hours, 1, 1, 1); + + bytes[] memory data = new bytes[](1); + data[0] = abi.encodeWithSignature("setEndingPrincipal(uint256)", 0); + + // Executing refinance + vm.prank(borrower); + loan.proposeNewTerms(address(1), block.timestamp, data); + + vm.prank(address(lender)); + vm.expectRevert("ML:ANT:INVALID_REFINANCER"); + loan.acceptNewTerms(address(1), block.timestamp, data); + } + +} + +contract MapleLoanRefinancerMultipleParameterTests is TestBase { + + function test_refinance_multipleParameters( + uint256 principalRequested_, + uint256 collateralRequired_, + uint256 endingPrincipal_, + uint256 gracePeriod_, + uint256 interestRate_, + uint256 paymentInterval_, + uint256 paymentsRemaining_, + uint256 deadline_ + ) + external + { + principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT - MIN_TOKEN_AMOUNT); + collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); + endingPrincipal_ = constrictToRange(endingPrincipal_, principalRequested_ / 2, principalRequested_); + gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); + interestRate_ = constrictToRange(interestRate_, 10_000, MAX_RATE / 2); // Giving enough room to increase the interest Rate + paymentInterval_ = constrictToRange(paymentInterval_, 30 days, MAX_TIME / 2); + paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); + + setUpOngoingLoan( + principalRequested_, + collateralRequired_, + endingPrincipal_, + gracePeriod_, + interestRate_, + paymentInterval_, + paymentsRemaining_ + ); + + deadline_ = constrictToRange(deadline_, block.timestamp, type(uint256).max); + + // Asserting state + assertEq(loan.collateralRequired(), collateralRequired_); + assertEq(loan.endingPrincipal(), endingPrincipal_); + assertEq(loan.gracePeriod(), gracePeriod_); + assertEq(loan.interestRate(), interestRate_); + assertEq(loan.paymentInterval(), paymentInterval_); + assertEq(loan.principalRequested(), principalRequested_); + + uint256 currentPrincipal = loan.principal(); + + // Defining refinance terms + uint256 newCollateralRequired_ = MIN_TOKEN_AMOUNT; + uint256 newEndingPrincipal_ = 0; + uint256 newGracePeriod_ = 95; + uint256 newInterestRate_ = 0; + uint256 newPaymentInterval_ = 15 days; + uint256 principalIncrease_ = MIN_TOKEN_AMOUNT; + + bytes[] memory data = new bytes[](6); + data[0] = abi.encodeWithSignature("setCollateralRequired(uint256)", newCollateralRequired_); + data[1] = abi.encodeWithSignature("setEndingPrincipal(uint256)", newEndingPrincipal_); + data[2] = abi.encodeWithSignature("setGracePeriod(uint256)", newGracePeriod_); + data[3] = abi.encodeWithSignature("setInterestRate(uint256)", newInterestRate_); + data[4] = abi.encodeWithSignature("setPaymentInterval(uint256)", newPaymentInterval_); + data[5] = abi.encodeWithSignature("increasePrincipal(uint256)", principalIncrease_); + + // Executing refinance + vm.prank(borrower); + loan.proposeNewTerms(address(refinancer), deadline_, data); + + uint256 currentCollateral = loan.collateral(); + + if (newCollateralRequired_ > currentCollateral) { + token.mint(address(loan), newCollateralRequired_ - currentCollateral); + loan.postCollateral(0); + } + + token.mint(address(loan), principalIncrease_); + + uint256 expectedRefinanceInterest = loan.getRefinanceInterest(block.timestamp); + + vm.prank(address(lender)); + loan.acceptNewTerms(address(refinancer), deadline_, data); + + assertEq(loan.collateralRequired(), newCollateralRequired_); + assertEq(loan.endingPrincipal(), newEndingPrincipal_); + assertEq(loan.gracePeriod(), newGracePeriod_); + assertEq(loan.interestRate(), newInterestRate_); + assertEq(loan.paymentInterval(), newPaymentInterval_); + assertEq(loan.principal(), currentPrincipal + principalIncrease_); + assertEq(loan.refinanceInterest(), expectedRefinanceInterest); + } + +} + +contract RefinanceCollateralRequiredTests is TestBase { function test_refinance_collateralRequired( uint256 principalRequested_, @@ -183,7 +291,7 @@ contract RefinancerCollateralRequiredTests is RefinancerTestBase { } -contract RefinancerDeadlineTests is RefinancerTestBase { +contract RefinanceDeadlineTests is TestBase { // Using payments interval since it's a rather easy refinance with no need to handle principal/collateral assets. function test_refinance_afterDeadline( @@ -256,7 +364,7 @@ contract RefinancerDeadlineTests is RefinancerTestBase { } -contract RefinancerEndingPrincipalTests is RefinancerTestBase { +contract RefinanceEndingPrincipalTests is TestBase { function test_refinance_endingPrincipal_interestOnlyToAmortized( uint256 principalRequested_, @@ -413,7 +521,7 @@ contract RefinancerEndingPrincipalTests is RefinancerTestBase { } -contract RefinancerFeeTests is RefinancerTestBase { +contract RefinanceFeeTests is TestBase { function test_refinance_closingRate( uint256 principalRequested_, @@ -564,7 +672,7 @@ contract RefinancerFeeTests is RefinancerTestBase { } -contract RefinancerGracePeriodTests is RefinancerTestBase { +contract RefinanceGracePeriodTests is TestBase { function test_refinance_gracePeriod( uint256 principalRequested_, @@ -616,7 +724,7 @@ contract RefinancerGracePeriodTests is RefinancerTestBase { } -contract RefinancerInterestRateTests is RefinancerTestBase { +contract RefinanceInterestRateTests is TestBase { function test_refinance_interestRate( uint256 principalRequested_, @@ -668,7 +776,7 @@ contract RefinancerInterestRateTests is RefinancerTestBase { } -contract RefinancerInterestTests is TestUtils { +contract RefinanceInterestTests is TestUtils { // Loan Boundaries uint256 internal constant MAX_PAYMENTS = 20; @@ -685,7 +793,7 @@ contract RefinancerInterestTests is TestUtils { MockFeeManager internal feeManager; MockGlobals internal globals; MockLoanManager internal lender; - Refinancer internal refinancer; + MapleRefinancer internal refinancer; address internal borrower = address(new Address()); address internal governor = address(new Address()); @@ -694,7 +802,7 @@ contract RefinancerInterestTests is TestUtils { feeManager = new MockFeeManager(); globals = new MockGlobals(address(governor)); lender = new MockLoanManager(); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); token = new MockERC20("Test", "TST", 0); factory = new MockFactory(address(globals)); @@ -817,115 +925,7 @@ contract RefinancerInterestTests is TestUtils { } -contract RefinancerMiscellaneousTests is RefinancerTestBase { - - function test_refinance_invalidRefinancer() external { - setUpOngoingLoan(1, 1, 1, 12 hours, 1, 1, 1); - - bytes[] memory data = new bytes[](1); - data[0] = abi.encodeWithSignature("setEndingPrincipal(uint256)", 0); - - // Executing refinance - vm.prank(borrower); - loan.proposeNewTerms(address(1), block.timestamp, data); - - vm.prank(address(lender)); - vm.expectRevert("ML:ANT:INVALID_REFINANCER"); - loan.acceptNewTerms(address(1), block.timestamp, data); - } - -} - -contract RefinancerMultipleParameterTests is RefinancerTestBase { - - function test_refinance_multipleParameters( - uint256 principalRequested_, - uint256 collateralRequired_, - uint256 endingPrincipal_, - uint256 gracePeriod_, - uint256 interestRate_, - uint256 paymentInterval_, - uint256 paymentsRemaining_, - uint256 deadline_ - ) - external - { - principalRequested_ = constrictToRange(principalRequested_, MIN_TOKEN_AMOUNT, MAX_TOKEN_AMOUNT - MIN_TOKEN_AMOUNT); - collateralRequired_ = constrictToRange(collateralRequired_, 0, MAX_TOKEN_AMOUNT); - endingPrincipal_ = constrictToRange(endingPrincipal_, principalRequested_ / 2, principalRequested_); - gracePeriod_ = constrictToRange(gracePeriod_, 12 hours, MAX_TIME); - interestRate_ = constrictToRange(interestRate_, 10_000, MAX_RATE / 2); // Giving enough room to increase the interest Rate - paymentInterval_ = constrictToRange(paymentInterval_, 30 days, MAX_TIME / 2); - paymentsRemaining_ = constrictToRange(paymentsRemaining_, 3, MAX_PAYMENTS); - - setUpOngoingLoan( - principalRequested_, - collateralRequired_, - endingPrincipal_, - gracePeriod_, - interestRate_, - paymentInterval_, - paymentsRemaining_ - ); - - deadline_ = constrictToRange(deadline_, block.timestamp, type(uint256).max); - - // Asserting state - assertEq(loan.collateralRequired(), collateralRequired_); - assertEq(loan.endingPrincipal(), endingPrincipal_); - assertEq(loan.gracePeriod(), gracePeriod_); - assertEq(loan.interestRate(), interestRate_); - assertEq(loan.paymentInterval(), paymentInterval_); - assertEq(loan.principalRequested(), principalRequested_); - - uint256 currentPrincipal = loan.principal(); - - // Defining refinance terms - uint256 newCollateralRequired_ = MIN_TOKEN_AMOUNT; - uint256 newEndingPrincipal_ = 0; - uint256 newGracePeriod_ = 95; - uint256 newInterestRate_ = 0; - uint256 newPaymentInterval_ = 15 days; - uint256 principalIncrease_ = MIN_TOKEN_AMOUNT; - - bytes[] memory data = new bytes[](6); - data[0] = abi.encodeWithSignature("setCollateralRequired(uint256)", newCollateralRequired_); - data[1] = abi.encodeWithSignature("setEndingPrincipal(uint256)", newEndingPrincipal_); - data[2] = abi.encodeWithSignature("setGracePeriod(uint256)", newGracePeriod_); - data[3] = abi.encodeWithSignature("setInterestRate(uint256)", newInterestRate_); - data[4] = abi.encodeWithSignature("setPaymentInterval(uint256)", newPaymentInterval_); - data[5] = abi.encodeWithSignature("increasePrincipal(uint256)", principalIncrease_); - - // Executing refinance - vm.prank(borrower); - loan.proposeNewTerms(address(refinancer), deadline_, data); - - uint256 currentCollateral = loan.collateral(); - - if (newCollateralRequired_ > currentCollateral) { - token.mint(address(loan), newCollateralRequired_ - currentCollateral); - loan.postCollateral(0); - } - - token.mint(address(loan), principalIncrease_); - - uint256 expectedRefinanceInterest = loan.getRefinanceInterest(block.timestamp); - - vm.prank(address(lender)); - loan.acceptNewTerms(address(refinancer), deadline_, data); - - assertEq(loan.collateralRequired(), newCollateralRequired_); - assertEq(loan.endingPrincipal(), newEndingPrincipal_); - assertEq(loan.gracePeriod(), newGracePeriod_); - assertEq(loan.interestRate(), newInterestRate_); - assertEq(loan.paymentInterval(), newPaymentInterval_); - assertEq(loan.principal(), currentPrincipal + principalIncrease_); - assertEq(loan.refinanceInterest(), expectedRefinanceInterest); - } - -} - -contract RefinancerPaymentIntervalTests is RefinancerTestBase { +contract RefinancePaymentIntervalTests is TestBase { function test_refinance_paymentInterval_zeroAmount() external { setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 12 hours, 0.1e6, 30 days, 6); @@ -999,7 +999,7 @@ contract RefinancerPaymentIntervalTests is RefinancerTestBase { } -contract RefinancerPaymentsRemainingTests is RefinancerTestBase { +contract RefinancePaymentsRemainingTests is TestBase { function test_refinance_paymentRemaining_zeroAmount() external { setUpOngoingLoan(MIN_TOKEN_AMOUNT, 0, MIN_TOKEN_AMOUNT, 12 hours, 0.1e6, 30 days, 6); @@ -1075,7 +1075,7 @@ contract RefinancerPaymentsRemainingTests is RefinancerTestBase { } -contract RefinancerPrincipalRequestedTests is RefinancerTestBase { +contract RefinancePrincipalRequestedTests is TestBase { // Saving as storage variables to avoid stack too deep uint256 initialDrawableFunds; @@ -1214,7 +1214,7 @@ contract RefinancerPrincipalRequestedTests is RefinancerTestBase { } -// Not Using RefinancerTestBase due to the need to use Mocks for the +// Not Using TestBase due to the need to use Mocks for the contract RefinancingFeesTerms is TestUtils { address internal POOL_DELEGATE = address(new Address()); @@ -1235,7 +1235,7 @@ contract RefinancingFeesTerms is TestUtils { MockGlobals internal globals; MockLoanManager internal lender; MockPoolManager internal poolManager; - Refinancer internal refinancer; + MapleRefinancer internal refinancer; address internal borrower = address(new Address()); address internal governor = address(new Address()); @@ -1244,7 +1244,7 @@ contract RefinancingFeesTerms is TestUtils { lender = new MockLoanManager(); globals = new MockGlobals(governor); poolManager = new MockPoolManager(address(POOL_DELEGATE)); - refinancer = new Refinancer(); + refinancer = new MapleRefinancer(); factory = new MockFactory(address(globals)); feeManager = new MapleLoanFeeManager(address(globals)); diff --git a/tests/MapleLoanV502Migrator.t.sol b/tests/MapleLoanV502Migrator.t.sol new file mode 100644 index 0000000..102bb00 --- /dev/null +++ b/tests/MapleLoanV502Migrator.t.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.7; + +import { Address, TestUtils } from "../modules/contract-test-utils/contracts/test.sol"; +import { MockERC20 } from "../modules/erc20/contracts/test/mocks/MockERC20.sol"; + +import { MapleLoan } from "../contracts/MapleLoan.sol"; +import { MapleLoanFactory} from "../contracts/MapleLoanFactory.sol"; +import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; +import { MapleLoanV502Migrator } from "../contracts/MapleLoanV502Migrator.sol"; + +import { MockGlobals, MockFeeManager, MockLoanManager, MockLoanManagerFactory } from "./mocks/Mocks.sol"; + +contract MapleLoanV502MigratorTests is TestUtils { + + address internal governor = address(new Address()); + address internal securityAdmin = address(new Address()); + address internal implementation501; + address internal implementation502; + address internal initializer; + + MapleLoan loan501; + MapleLoan loan502; + MapleLoanFactory oldFactory; + MapleLoanFactory newFactory; + MapleLoanV502Migrator migrator; + MockERC20 asset; + MockFeeManager feeManager; + MockGlobals globals; + MockLoanManager lender; + MockLoanManagerFactory loanManagerFactory; + + function setUp() external { + asset = new MockERC20("Asset", "ASSET", 18); + globals = new MockGlobals(governor); + feeManager = new MockFeeManager(); + implementation501 = address(new MapleLoan()); + implementation502 = address(new MapleLoan()); + initializer = address(new MapleLoanInitializer()); + lender = new MockLoanManager(); + migrator = new MapleLoanV502Migrator(); + oldFactory = new MapleLoanFactory(address(globals), address(0)); + + loanManagerFactory = MockLoanManagerFactory(lender.factory()); + + lender.__setFundsAsset(address(asset)); + + globals.setValidBorrower(address(1), true); + globals.setValidCollateralAsset(address(asset), true); + globals.setValidPoolAsset(address(asset), true); + globals.__setIsInstanceOf(true); + globals.__setCanDeploy(true); + globals.__setSecurityAdmin(securityAdmin); + + vm.startPrank(governor); + oldFactory.registerImplementation(501, implementation501, initializer); + oldFactory.setDefaultVersion(501); + oldFactory.registerImplementation(502, implementation502, initializer); + oldFactory.enableUpgradePath(501, 502, address(migrator)); + vm.stopPrank(); + + address[2] memory assets = [address(asset), address(asset)]; + uint256[3] memory termDetails = [uint256(12 hours), uint256(365 days), uint256(1)]; + uint256[3] memory amounts = [uint256(0), uint256(1_000_000e6), uint256(1_000_000e6)]; + uint256[4] memory rates = [uint256(0.1e18), uint256(0.02e18), uint256(0.03e18), uint256(0.04e18)]; + uint256[2] memory fees = [uint256(0), uint256(0)]; + + bytes memory arguments = MapleLoanInitializer(initializer).encodeArguments( + address(1), + address(lender), + address(feeManager), + assets, + termDetails, + amounts, + rates, + fees + ); + + loan501 = MapleLoan(oldFactory.createInstance(arguments, "SALT1")); + + asset.mint(address(loan501), 1_000_000e6); + + vm.prank(address(lender)); + loan501.fundLoan(); + + loan502 = MapleLoan(oldFactory.createInstance(arguments, "SALT2")); + } + + function test_migration_sameFactory_noOp() external { + newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); + + bytes memory arguments = abi.encode(address(oldFactory)); + + vm.expectRevert("MPF:UI:FAILED"); + vm.prank(securityAdmin); + loan501.upgrade(502, arguments); + } + + function test_migration_invalidFactory() external { + newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); + + bytes memory arguments = abi.encode(address(newFactory)); + + globals.__setIsInstanceOf(false); + + vm.expectRevert("MPF:UI:FAILED"); + vm.prank(securityAdmin); + loan501.upgrade(502, arguments); + } + + function test_migration_factoryChange() external { + assertEq(loan501.factory(), address(oldFactory)); + assertEq(loan501.implementation(), address(implementation501)); + + newFactory = new MapleLoanFactory(address(globals), address(oldFactory)); + + bytes memory arguments = abi.encode(address(newFactory)); + + // Upgrade + vm.prank(securityAdmin); + loan501.upgrade(502, arguments); + + // Set loan502 using loan501 as it is now upgraded. + loan502 = loan501; + + assertEq(loan502.factory(), address(newFactory)); + assertEq(loan502.implementation(), address(implementation502)); + assertEq(loan502.fundsAsset(), address(asset)); + + assertEq(loan502.principal(), 1_000_000e6); + } + +} diff --git a/tests/Payments.t.sol b/tests/Payments.t.sol index de221c9..696fe31 100644 --- a/tests/Payments.t.sol +++ b/tests/Payments.t.sol @@ -11,7 +11,7 @@ import { MapleLoanInitializer } from "../contracts/MapleLoanInitializer.sol"; import { MockGlobals, MockLoanManager, MockPoolManager } from "./mocks/Mocks.sol"; -contract MapleLoanPaymentsTestBase is TestUtils { +contract TestBase is TestUtils { uint256 internal start; @@ -210,7 +210,7 @@ contract MapleLoanPaymentsTestBase is TestUtils { } -contract ClosingTests is MapleLoanPaymentsTestBase { +contract ClosingTests is TestBase { function test_payments_closing_flatRate_case1() external { /****************************************/ @@ -473,7 +473,7 @@ contract ClosingTests is MapleLoanPaymentsTestBase { } -contract FullyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { +contract FullyAmortizedPaymentsTests is TestBase { function test_payments_fullyAmortized_case1() external { /****************************************/ @@ -677,7 +677,7 @@ contract FullyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { } -contract InterestOnlyPaymentsTests is MapleLoanPaymentsTestBase { +contract InterestOnlyPaymentsTests is TestBase { function test_payments_interestOnly_case1() external { /****************************************/ @@ -877,7 +877,7 @@ contract InterestOnlyPaymentsTests is MapleLoanPaymentsTestBase { } -contract LateRepaymentsTests is MapleLoanPaymentsTestBase { +contract LateRepaymentsTests is TestBase { function test_payments_lateRepayment_flatRate_case1() external { /****************************************/ @@ -1524,7 +1524,7 @@ contract LateRepaymentsTests is MapleLoanPaymentsTestBase { } -contract PartiallyAmortizedPaymentsTests is MapleLoanPaymentsTestBase { +contract PartiallyAmortizedPaymentsTests is TestBase { function test_payments_partiallyAmortized_case1() external { /****************************************/ diff --git a/tests/mocks/Mocks.sol b/tests/mocks/Mocks.sol index ab81128..f98b868 100644 --- a/tests/mocks/Mocks.sol +++ b/tests/mocks/Mocks.sol @@ -21,6 +21,7 @@ contract MockGlobals { mapping(address => bool) public isPoolAsset; bool internal _isInstanceOf; + bool internal _canDeploy; constructor (address governor_) { governor = governor_; @@ -34,6 +35,10 @@ contract MockGlobals { return _isInstanceOf; } + function canDeploy(address) external view returns (bool) { + return _canDeploy; + } + function setGovernor(address governor_) external { governor = governor_; } @@ -74,6 +79,10 @@ contract MockGlobals { _isInstanceOf = isInstanceOf_; } + function __setCanDeploy(bool canDeploy_) external { + _canDeploy = canDeploy_; + } + function __setSecurityAdmin(address securityAdmin_) external { securityAdmin = securityAdmin_; } @@ -262,3 +271,12 @@ contract RevertingERC20 { } } + +contract MockLoanFactory { + mapping(address => bool) public isLoan; + + function __setIsLoan(address loan_, bool isLoan_) external { + isLoan[loan_] = isLoan_; + } + +}