From 7f19c6b0cb368266c9741884da9944d593cdaf00 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Fri, 11 Aug 2023 15:14:54 +0200 Subject: [PATCH 01/19] test(bulker): add blue tests --- remappings.txt | 8 ++--- test/forge/BaseBulkerTest.sol | 4 +-- test/forge/EVMBulker.t.sol | 56 +++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/remappings.txt b/remappings.txt index b65f77ff..1c33a247 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,12 +1,12 @@ contracts/=contracts/ test/=test/ -solmate/=lib/morpho-blue/lib/solmate/ - -@forge-std/=lib/morpho-blue/lib/forge-std/src/ @morpho-blue/=lib/morpho-blue/src/ @solmate/=lib/morpho-blue/lib/solmate/src/ +@forge-std/=lib/morpho-blue/lib/forge-std/src/ +solmate/=lib/morpho-blue/lib/solmate/ + +@permit2/=lib/permit2/src/ @morpho-utils/=lib/morpho-utils/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ -@permit2/=lib/permit2/src/ @uniswap/v3-periphery=lib/v3-periphery/contracts/ diff --git a/test/forge/BaseBulkerTest.sol b/test/forge/BaseBulkerTest.sol index 4a7eac52..804ab32d 100644 --- a/test/forge/BaseBulkerTest.sol +++ b/test/forge/BaseBulkerTest.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -// import {SigUtils} from "@morpho-blue/../test/helpers/SigUtils.sol"; - import "@morpho-blue/Blue.sol"; import {ERC20Mock} from "./mocks/ERC20Mock.sol"; import {OracleMock} from "@morpho-blue/mocks/OracleMock.sol"; import {IrmMock} from "@morpho-blue/mocks/IrmMock.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + import "@forge-std/Test.sol"; import "@forge-std/console2.sol"; diff --git a/test/forge/EVMBulker.t.sol b/test/forge/EVMBulker.t.sol index 55956866..90404c37 100644 --- a/test/forge/EVMBulker.t.sol +++ b/test/forge/EVMBulker.t.sol @@ -38,6 +38,62 @@ contract EVMBulkerTest is BaseBulkerTest { /* TESTS */ + function testSetAuthorization(uint256 privateKey, uint32 deadline) public { + privateKey = bound(privateKey, 1, type(uint32).max); + deadline = uint32(bound(deadline, block.timestamp + 1, type(uint32).max)); + + address user = vm.addr(privateKey); + vm.assume(user != USER); + + bytes32 digest = ECDSA.toTypedDataHash( + blue.DOMAIN_SEPARATOR(), + keccak256(abi.encode(AUTHORIZATION_TYPEHASH, user, address(bulker), true, blue.nonce(user), deadline)) + ); + + Signature memory sig; + (sig.v, sig.r, sig.s) = vm.sign(privateKey, digest); + + bytes[] memory data = new bytes[](1); + data[0] = abi.encodeCall(BlueBulker.blueSetAuthorization, (user, true, deadline, sig)); + + bulker.multicall(block.timestamp, data); + + assertTrue(blue.isAuthorized(user, address(bulker)), "isAuthorized(bulker)"); + } + + function testSupply(uint256 amount, address onBehalf) public { + vm.assume(onBehalf != address(0)); + vm.assume(onBehalf != address(blue)); + vm.assume(onBehalf != address(bulker)); + + amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); + + bytes[] memory data = new bytes[](2); + data[0] = abi.encodeCall(ERC20Bulker.transferFrom2, (address(borrowableAsset), amount)); + data[1] = abi.encodeCall(BlueBulker.blueSupply, (market, amount, 0, onBehalf, hex"")); + + borrowableAsset.setBalance(USER, amount); + + vm.prank(USER); + bulker.multicall(block.timestamp, data); + + assertEq(collateralAsset.balanceOf(USER), 0, "collateral.balanceOf(USER)"); + assertEq(borrowableAsset.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); + + assertEq(collateralAsset.balanceOf(onBehalf), 0, "collateral.balanceOf(onBehalf)"); + assertEq(borrowableAsset.balanceOf(onBehalf), 0, "borrowable.balanceOf(onBehalf)"); + + assertEq(blue.collateral(id, onBehalf), 0, "collateral(onBehalf)"); + assertEq(blue.supplyShares(id, onBehalf), amount * SharesMathLib.VIRTUAL_SHARES, "supplyShares(onBehalf)"); + assertEq(blue.borrowShares(id, onBehalf), 0, "borrowShares(onBehalf)"); + + if (onBehalf != USER) { + assertEq(blue.collateral(id, USER), 0, "collateral(USER)"); + assertEq(blue.supplyShares(id, USER), 0, "supplyShares(USER)"); + assertEq(blue.borrowShares(id, USER), 0, "borrowShares(USER)"); + } + } + function _testSupplyCollateralBorrow(uint256 amount, uint256 collateralAmount, address receiver) internal { assertEq(collateralAsset.balanceOf(USER), 0, "collateral.balanceOf(USER)"); assertEq(borrowableAsset.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); From fae7b4782caed3599f417f6400bbc84da4bcde71 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Fri, 11 Aug 2023 16:18:52 +0200 Subject: [PATCH 02/19] test(bulker): add fork test --- .env.example | 2 + Makefile | 6 ++ config/ConfigLib.sol | 53 ++++++++++ config/Configured.sol | 58 +++++++++++ config/ethereum-mainnet.json | 20 ++++ foundry.toml | 5 + remappings.txt | 1 + test/forge/EVMBulker.t.sol | 4 +- test/forge/ethereum-mainnet/EVMBulker.t.sol | 75 ++++++++++++++ test/forge/helpers/BaseTest.sol | 45 +++++++++ test/forge/helpers/ForkTest.sol | 99 +++++++++++++++++++ .../LocalTest.sol} | 39 +++----- 12 files changed, 381 insertions(+), 26 deletions(-) create mode 100644 .env.example create mode 100644 config/ConfigLib.sol create mode 100644 config/Configured.sol create mode 100644 config/ethereum-mainnet.json create mode 100644 test/forge/ethereum-mainnet/EVMBulker.t.sol create mode 100644 test/forge/helpers/BaseTest.sol create mode 100644 test/forge/helpers/ForkTest.sol rename test/forge/{BaseBulkerTest.sol => helpers/LocalTest.sol} (57%) diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..2784a786 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NETWORK=ethereum-mainnet +ALCHEMY_KEY= diff --git a/Makefile b/Makefile index 07bcc361..26de9d5f 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,12 @@ contracts: FOUNDRY_TEST=/dev/null FOUNDRY_SCRIPT=/dev/null forge build --via-ir --extra-output-files irOptimized --sizes --force +test-mainnet: + @FOUNDRY_MATCH_CONTRACT=EthereumTest make test + +test-local: + @FOUNDRY_MATCH_CONTRACT=LocalTest make test + test: forge test -vvv diff --git a/config/ConfigLib.sol b/config/ConfigLib.sol new file mode 100644 index 00000000..b8079875 --- /dev/null +++ b/config/ConfigLib.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import {stdJson} from "@forge-std/StdJson.sol"; + +struct Config { + string json; +} + +library ConfigLib { + using stdJson for string; + + string internal constant CHAIN_ID_PATH = "$.chainId"; + string internal constant RPC_ALIAS_PATH = "$.rpcAlias"; + string internal constant FORK_BLOCK_NUMBER_PATH = "$.forkBlockNumber"; + string internal constant WRAPPED_NATIVE_PATH = "$.wrappedNative"; + string internal constant LSD_NATIVES_PATH = "$.lsdNatives"; + + function getAddress(Config storage config, string memory key) internal returns (address) { + return config.json.readAddress(string.concat("$.", key)); + } + + function getAddressArray(Config storage config, string[] memory keys) + internal + returns (address[] memory addresses) + { + addresses = new address[](keys.length); + + for (uint256 i; i < keys.length; ++i) { + addresses[i] = getAddress(config, keys[i]); + } + } + + function getChainId(Config storage config) internal returns (uint256) { + return config.json.readUint(CHAIN_ID_PATH); + } + + function getRpcAlias(Config storage config) internal returns (string memory) { + return config.json.readString(RPC_ALIAS_PATH); + } + + function getForkBlockNumber(Config storage config) internal returns (uint256) { + return config.json.readUint(FORK_BLOCK_NUMBER_PATH); + } + + function getWrappedNative(Config storage config) internal returns (address) { + return getAddress(config, config.json.readString(WRAPPED_NATIVE_PATH)); + } + + function getLsdNatives(Config storage config) internal returns (address[] memory) { + return getAddressArray(config, config.json.readStringArray(LSD_NATIVES_PATH)); + } +} diff --git a/config/Configured.sol b/config/Configured.sol new file mode 100644 index 00000000..f29e14ff --- /dev/null +++ b/config/Configured.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import {Config, ConfigLib} from "config/ConfigLib.sol"; + +import {StdChains, VmSafe} from "@forge-std/StdChains.sol"; + +abstract contract Configured is StdChains { + using ConfigLib for Config; + + VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); + + Config internal config; + + address internal dai; + address internal usdc; + address internal usdt; + address internal link; + address internal wbtc; + address internal weth; + address internal wNative; + address[] internal lsdNatives; + address[] internal allAssets; + + function _network() internal view virtual returns (string memory); + + function _rpcAlias() internal virtual returns (string memory) { + return config.getRpcAlias(); + } + + function _initConfig() internal returns (Config storage) { + if (bytes(config.json).length == 0) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/config/", _network(), ".json"); + + config.json = vm.readFile(path); + } + + return config; + } + + function _loadConfig() internal virtual { + dai = config.getAddress("DAI"); + usdc = config.getAddress("USDC"); + usdt = config.getAddress("USDT"); + link = config.getAddress("LINK"); + wbtc = config.getAddress("WBTC"); + weth = config.getAddress("WETH"); + wNative = config.getWrappedNative(); + lsdNatives = config.getLsdNatives(); + + allAssets = [dai, usdc, usdt, link, wbtc, weth]; + + for (uint256 i; i < lsdNatives.length; ++i) { + allAssets.push(lsdNatives[i]); + } + } +} diff --git a/config/ethereum-mainnet.json b/config/ethereum-mainnet.json new file mode 100644 index 00000000..39f8fc7e --- /dev/null +++ b/config/ethereum-mainnet.json @@ -0,0 +1,20 @@ +{ + "chainId": 1, + "rpcAlias": "mainnet", + "forkBlockNumber": 17891982, + "DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "LINK": "0x514910771AF9Ca656af840dff83E8264EcF986CA", + "WBTC": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "wstETH": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "cbETH": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + "rETH": "0xae78736Cd615f374D3085123A210448E74Fc6393", + "wrappedNative": "WETH", + "lsdNatives": [ + "wstETH", + "cbETH", + "rETH" + ] +} diff --git a/foundry.toml b/foundry.toml index 7ca0262e..baef7e6b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,11 @@ src = "contracts" out = "out" libs = ["lib"] +fs_permissions = [{ access = "read", path = "./config/"}] via-ir = true +[rpc_endpoints] +mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}" +tenderly = "https://rpc.tenderly.co/fork/${TENDERLY_FORK_ID}" + # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/remappings.txt b/remappings.txt index 1c33a247..117ff997 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,4 @@ +config/=config/ contracts/=contracts/ test/=test/ diff --git a/test/forge/EVMBulker.t.sol b/test/forge/EVMBulker.t.sol index 90404c37..3211c817 100644 --- a/test/forge/EVMBulker.t.sol +++ b/test/forge/EVMBulker.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.0; import "contracts/bulkers/EVMBulker.sol"; -import "./BaseBulkerTest.sol"; +import "./helpers/LocalTest.sol"; -contract EVMBulkerTest is BaseBulkerTest { +contract EVMBulkerLocalTest is LocalTest { using FixedPointMathLib for uint256; EVMBulker private bulker; diff --git a/test/forge/ethereum-mainnet/EVMBulker.t.sol b/test/forge/ethereum-mainnet/EVMBulker.t.sol new file mode 100644 index 00000000..ae1f5930 --- /dev/null +++ b/test/forge/ethereum-mainnet/EVMBulker.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "contracts/bulkers/EVMBulker.sol"; + +import "../helpers/ForkTest.sol"; + +contract EVMBulkerEthereumTest is ForkTest { + using FixedPointMathLib for uint256; + + EVMBulker private bulker; + + function _network() internal pure override returns (string memory) { + return "ethereum-mainnet"; + } + + function setUp() public override { + super.setUp(); + + bulker = new EVMBulker(address(blue)); + + vm.startPrank(USER); + blue.setAuthorization(address(bulker), true); + blue.setAuthorization(address(this), true); // So tests can borrow/withdraw on behalf of USER without pranking it. + vm.stopPrank(); + } + + /* INVARIANTS */ + + function invariantBulkerBalanceOfZero() public { + // assertEq(collateralAsset.balanceOf(address(bulker)), 0, "collateral.balanceOf(bulker)"); + // assertEq(borrowableAsset.balanceOf(address(bulker)), 0, "borrowable.balanceOf(bulker)"); + } + + function invariantBulkerPositionZero() public { + // assertEq(blue.collateral(id, address(bulker)), 0, "collateral(bulker)"); + // assertEq(blue.supplyShares(id, address(bulker)), 0, "supplyShares(bulker)"); + // assertEq(blue.borrowShares(id, address(bulker)), 0, "borrowShares(bulker)"); + } + + /* TESTS */ + + function testSupply(uint256 amount, address onBehalf) public { + vm.assume(onBehalf != address(0)); + vm.assume(onBehalf != address(blue)); + vm.assume(onBehalf != address(bulker)); + + amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); + + // bytes[] memory data = new bytes[](2); + // data[0] = abi.encodeCall(ERC20Bulker.transferFrom2, (address(borrowableAsset), amount)); + // data[1] = abi.encodeCall(BlueBulker.blueSupply, (market, amount, 0, onBehalf, hex"")); + + // borrowableAsset.setBalance(USER, amount); + + // vm.prank(USER); + // bulker.multicall(block.timestamp, data); + + // assertEq(collateralAsset.balanceOf(USER), 0, "collateral.balanceOf(USER)"); + // assertEq(borrowableAsset.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); + + // assertEq(collateralAsset.balanceOf(onBehalf), 0, "collateral.balanceOf(onBehalf)"); + // assertEq(borrowableAsset.balanceOf(onBehalf), 0, "borrowable.balanceOf(onBehalf)"); + + // assertEq(blue.collateral(id, onBehalf), 0, "collateral(onBehalf)"); + // assertEq(blue.supplyShares(id, onBehalf), amount * SharesMathLib.VIRTUAL_SHARES, "supplyShares(onBehalf)"); + // assertEq(blue.borrowShares(id, onBehalf), 0, "borrowShares(onBehalf)"); + + // if (onBehalf != USER) { + // assertEq(blue.collateral(id, USER), 0, "collateral(USER)"); + // assertEq(blue.supplyShares(id, USER), 0, "supplyShares(USER)"); + // assertEq(blue.borrowShares(id, USER), 0, "borrowShares(USER)"); + // } + } +} diff --git a/test/forge/helpers/BaseTest.sol b/test/forge/helpers/BaseTest.sol new file mode 100644 index 00000000..d046ab1e --- /dev/null +++ b/test/forge/helpers/BaseTest.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {Id, Market, Signature, IBlue} from "@morpho-blue/interfaces/IBlue.sol"; + +import {MarketLib} from "@morpho-blue/libraries/MarketLib.sol"; +import {SharesMathLib} from "@morpho-blue/libraries/SharesMathLib.sol"; +import {FixedPointMathLib, WAD} from "@morpho-blue/libraries/FixedPointMathLib.sol"; +import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +import {Blue, AUTHORIZATION_TYPEHASH} from "@morpho-blue/Blue.sol"; +import {ERC20Mock} from "test/forge/mocks/ERC20Mock.sol"; +import {OracleMock} from "@morpho-blue/mocks/OracleMock.sol"; +import {IrmMock} from "@morpho-blue/mocks/IrmMock.sol"; + +import "@forge-std/Test.sol"; +import "@forge-std/console2.sol"; + +abstract contract BaseTest is Test { + using MarketLib for Market; + using SharesMathLib for uint256; + using stdStorage for StdStorage; + using FixedPointMathLib for uint256; + using SafeTransferLib for ERC20; + + uint256 internal constant MIN_AMOUNT = 1000; + uint256 internal constant MAX_AMOUNT = 2 ** 64; + + address internal constant USER = address(0x1234); + address internal constant SUPPLIER = address(0x5678); + address internal constant OWNER = address(0xdead); + + Blue internal blue; + IrmMock internal irm; + + function setUp() public virtual { + blue = new Blue(OWNER); + + irm = new IrmMock(blue); + + vm.prank(OWNER); + blue.enableIrm(address(irm)); + } +} diff --git a/test/forge/helpers/ForkTest.sol b/test/forge/helpers/ForkTest.sol new file mode 100644 index 00000000..4e693ea9 --- /dev/null +++ b/test/forge/helpers/ForkTest.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import {Config, ConfigLib} from "config/ConfigLib.sol"; + +import {Configured} from "config/Configured.sol"; + +import "./BaseTest.sol"; + +abstract contract ForkTest is BaseTest, Configured { + using ConfigLib for Config; + using SafeTransferLib for ERC20; + + /* STORAGE */ + + string internal network; + uint256 internal forkId; + + uint256 internal snapshotId = type(uint256).max; + + constructor() { + _initConfig(); + _loadConfig(); + + _setBalances(address(this), type(uint96).max); + } + + function setUp() public virtual override { + _label(); + + super.setUp(); + } + + function _fork() internal virtual { + string memory rpcUrl = vm.rpcUrl(_rpcAlias()); + uint256 forkBlockNumber = config.getForkBlockNumber(); + + forkId = forkBlockNumber == 0 ? vm.createSelectFork(rpcUrl) : vm.createSelectFork(rpcUrl, forkBlockNumber); + vm.chainId(config.getChainId()); + } + + function _loadConfig() internal virtual override { + super._loadConfig(); + + _fork(); + } + + function _label() internal virtual { + for (uint256 i; i < allAssets.length; ++i) { + address asset = allAssets[i]; + string memory symbol = ERC20(asset).symbol(); + + vm.label(asset, symbol); + } + } + + function _setBalances(address user, uint256 balance) internal { + for (uint256 i; i < allAssets.length; ++i) { + address asset = allAssets[i]; + + deal(asset, user, balance / (10 ** (18 - ERC20(asset).decimals()))); + } + } + + /// @dev Avoids to revert because of AAVE token snapshots: https://github.com/aave/aave-token-v2/blob/master/contracts/token/base/GovernancePowerDelegationERC20.sol#L174 + function _deal(address asset, address user, uint256 amount) internal { + if (amount == 0) return; + + if (asset == weth) deal(weth, weth.balance + amount); // Refill wrapped Ether. + + deal(asset, user, amount); + } + + /// @dev Reverts the fork to its initial fork state. + function _revert() internal { + if (snapshotId < type(uint256).max) vm.revertTo(snapshotId); + snapshotId = vm.snapshot(); + } + + function _assumeNotAsset(address input) internal view { + for (uint256 i; i < allAssets.length; ++i) { + vm.assume(input != allAssets[i]); + } + } + + function _assumeNotLsdNative(address input) internal view { + for (uint256 i; i < lsdNatives.length; ++i) { + vm.assume(input != lsdNatives[i]); + } + } + + function _randomAsset(uint256 seed) internal view returns (address) { + return allAssets[seed % allAssets.length]; + } + + function _randomLsdNative(uint256 seed) internal view returns (address) { + return lsdNatives[seed % lsdNatives.length]; + } +} diff --git a/test/forge/BaseBulkerTest.sol b/test/forge/helpers/LocalTest.sol similarity index 57% rename from test/forge/BaseBulkerTest.sol rename to test/forge/helpers/LocalTest.sol index 804ab32d..1762f745 100644 --- a/test/forge/BaseBulkerTest.sol +++ b/test/forge/helpers/LocalTest.sol @@ -1,60 +1,51 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "@morpho-blue/Blue.sol"; -import {ERC20Mock} from "./mocks/ERC20Mock.sol"; -import {OracleMock} from "@morpho-blue/mocks/OracleMock.sol"; -import {IrmMock} from "@morpho-blue/mocks/IrmMock.sol"; +import {Id, Market, Signature, IBlue} from "@morpho-blue/interfaces/IBlue.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MarketLib} from "@morpho-blue/libraries/MarketLib.sol"; +import {SharesMathLib} from "@morpho-blue/libraries/SharesMathLib.sol"; +import {FixedPointMathLib, WAD} from "@morpho-blue/libraries/FixedPointMathLib.sol"; +import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol"; -import "@forge-std/Test.sol"; -import "@forge-std/console2.sol"; +import {ERC20Mock} from "test/forge/mocks/ERC20Mock.sol"; -contract BaseBulkerTest is Test { +import "./BaseTest.sol"; + +abstract contract LocalTest is BaseTest { using MarketLib for Market; using SharesMathLib for uint256; using stdStorage for StdStorage; using FixedPointMathLib for uint256; + using SafeTransferLib for ERC20; - uint256 internal constant MIN_AMOUNT = 1000; - uint256 internal constant MAX_AMOUNT = 2 ** 64; - - address internal constant USER = address(0x1234); - address internal constant SUPPLIER = address(0x5678); uint256 internal constant LLTV = 0.8 ether; - address internal constant OWNER = address(0xdead); - Blue internal blue; ERC20Mock internal borrowableAsset; ERC20Mock internal collateralAsset; OracleMock internal oracle; - IrmMock internal irm; + Market internal market; Id internal id; - function setUp() public virtual { - // Create Blue. - blue = new Blue(OWNER); + function setUp() public virtual override { + super.setUp(); // List a market. borrowableAsset = new ERC20Mock("borrowable", "B", 18); collateralAsset = new ERC20Mock("collateral", "C", 18); oracle = new OracleMock(); - irm = new IrmMock(blue); - market = Market(address(borrowableAsset), address(collateralAsset), address(oracle), address(irm), LLTV); id = market.id(); + oracle.setPrice(WAD); + vm.startPrank(OWNER); - blue.enableIrm(address(irm)); blue.enableLltv(LLTV); blue.createMarket(market); vm.stopPrank(); - oracle.setPrice(WAD); - borrowableAsset.approve(address(blue), type(uint256).max); collateralAsset.approve(address(blue), type(uint256).max); From 8336a19380f733061a9a539e8510caf902050024 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 22 Aug 2023 08:39:52 +0200 Subject: [PATCH 03/19] refactor(test): add markets config --- config/ethereum-mainnet.json | 7 +++++++ contracts/meta-morpho/SupplyVault.sol | 4 ++-- foundry.toml | 4 ++++ test/forge/ethereum-mainnet/EVMBundler.t.sol | 9 ++++++--- test/forge/helpers/ForkTest.sol | 18 ++++++++++++++++-- test/forge/helpers/LocalTest.sol | 3 +-- 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/config/ethereum-mainnet.json b/config/ethereum-mainnet.json index 39f8fc7e..4c20dd00 100644 --- a/config/ethereum-mainnet.json +++ b/config/ethereum-mainnet.json @@ -2,6 +2,13 @@ "chainId": 1, "rpcAlias": "mainnet", "forkBlockNumber": 17891982, + "markets": [ + { + "collateralAsset": "DAI", + "borrowableAsset": "WETH", + "chainlinkOracle": "0x773616E4d11A78F511299002da57A0a94577F1f4" + } + ], "DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", diff --git a/contracts/meta-morpho/SupplyVault.sol b/contracts/meta-morpho/SupplyVault.sol index aa9f4d44..77f24e8d 100644 --- a/contracts/meta-morpho/SupplyVault.sol +++ b/contracts/meta-morpho/SupplyVault.sol @@ -16,7 +16,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {InternalSupplyRouter, ERC2771Context} from "./InternalSupplyRouter.sol"; import {IERC20, ERC20, ERC4626, Context} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -contract SupplyVault is ISupplyVault, ERC4626, Ownable, InternalSupplyRouter { +contract SupplyVault is ISupplyVault, InternalSupplyRouter, ERC4626, Ownable { using MorphoLib for IMorpho; using SharesMathLib for uint256; using ConfigSetLib for ConfigSet; @@ -28,9 +28,9 @@ contract SupplyVault is ISupplyVault, ERC4626, Ownable, InternalSupplyRouter { ConfigSet private _config; constructor(address morpho, address forwarder, string memory name_, string memory symbol_, IERC20 asset_) + InternalSupplyRouter(morpho, forwarder) ERC20(name_, symbol_) ERC4626(asset_) - InternalSupplyRouter(morpho, forwarder) {} modifier onlyRiskManager() { diff --git a/foundry.toml b/foundry.toml index baef7e6b..0b200e2d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,6 +5,10 @@ libs = ["lib"] fs_permissions = [{ access = "read", path = "./config/"}] via-ir = true +[invariant] +runs = 16 +depth = 4 + [rpc_endpoints] mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}" tenderly = "https://rpc.tenderly.co/fork/${TENDERLY_FORK_ID}" diff --git a/test/forge/ethereum-mainnet/EVMBundler.t.sol b/test/forge/ethereum-mainnet/EVMBundler.t.sol index b5b7ba4c..59e6991f 100644 --- a/test/forge/ethereum-mainnet/EVMBundler.t.sol +++ b/test/forge/ethereum-mainnet/EVMBundler.t.sol @@ -29,8 +29,11 @@ contract EVMBundlerEthereumTest is ForkTest { /* INVARIANTS */ function invariantBundlerBalanceOfZero() public { - // assertEq(collateralAsset.balanceOf(address(bundler)), 0, "collateral.balanceOf(bundler)"); - // assertEq(borrowableAsset.balanceOf(address(bundler)), 0, "borrowable.balanceOf(bundler)"); + for (uint256 i; i < allAssets.length; ++i) { + ERC20 asset = ERC20(allAssets[i]); + + assertEq(asset.balanceOf(address(bundler)), 0, "asset.balanceOf(bundler)"); + } } function invariantBundlerPositionZero() public { @@ -41,7 +44,7 @@ contract EVMBundlerEthereumTest is ForkTest { /* TESTS */ - function testSupply(uint256 amount, address onBehalf) public { + function testSupplyWithPermit2(uint256 amount, address onBehalf) public { vm.assume(onBehalf != address(0)); vm.assume(onBehalf != address(morpho)); vm.assume(onBehalf != address(bundler)); diff --git a/test/forge/helpers/ForkTest.sol b/test/forge/helpers/ForkTest.sol index 4e693ea9..f2c334a3 100644 --- a/test/forge/helpers/ForkTest.sol +++ b/test/forge/helpers/ForkTest.sol @@ -11,13 +11,13 @@ abstract contract ForkTest is BaseTest, Configured { using ConfigLib for Config; using SafeTransferLib for ERC20; - /* STORAGE */ - string internal network; uint256 internal forkId; uint256 internal snapshotId = type(uint256).max; + MarketParams[] markets; + constructor() { _initConfig(); _loadConfig(); @@ -29,6 +29,20 @@ abstract contract ForkTest is BaseTest, Configured { _label(); super.setUp(); + + // for (uint256 i; i < allAssets.length; ++i) { + // vm.startPrank(OWNER); + // morpho.createMarket( + // Market({ + // collateralAsset: allAssets[i], + // borrowableAsset: usdc, + // oracle: address(oracle), + // irm: address(irm), + // lltv: LLTV + // }) + // ); + // vm.stopPrank(); + // } } function _fork() internal virtual { diff --git a/test/forge/helpers/LocalTest.sol b/test/forge/helpers/LocalTest.sol index 848198a1..1d341cdb 100644 --- a/test/forge/helpers/LocalTest.sol +++ b/test/forge/helpers/LocalTest.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -// import {SigUtils} from "@morpho-blue/../test/helpers/SigUtils.sol"; - import {ERC20Mock} from "test/forge/mocks/ERC20Mock.sol"; import "./BaseTest.sol"; @@ -40,6 +38,7 @@ abstract contract LocalTest is BaseTest { vm.startPrank(OWNER); morpho.enableLltv(LLTV); + morpho.enableIrm(address(irm)); morpho.createMarket(marketParams); vm.stopPrank(); From 35104fbe2c78a9f39ee62d2c971ecb90593449da Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 22 Aug 2023 12:10:06 +0200 Subject: [PATCH 04/19] refactor(config): complete config markets --- .gitmodules | 1 + config/ConfigLib.sol | 32 +++++++++++++++++ config/Configured.sol | 6 +++- config/ethereum-mainnet.json | 7 ++-- contracts/BaseCallbackReceiver.sol | 2 +- contracts/BaseSelfMulticall.sol | 2 +- contracts/bundlers/BaseBundler.sol | 2 +- contracts/bundlers/ERC20Bundler.sol | 2 +- contracts/bundlers/ERC4626Bundler.sol | 2 +- contracts/bundlers/EVMBundler.sol | 2 +- contracts/bundlers/MorphoBundler.sol | 2 +- contracts/bundlers/OneInchBundler.sol | 2 +- contracts/bundlers/WNativeBundler.sol | 2 +- .../EthereumWrapperBundler.sol | 2 +- .../ethereum-mainnet/StEthBundler.sol | 2 +- .../meta-morpho/InternalSupplyRouter.sol | 2 +- contracts/meta-morpho/SupplyVault.sol | 2 +- contracts/routers/flash/AaveFlashRouter.sol | 2 +- contracts/routers/flash/AaveV2FlashRouter.sol | 2 +- contracts/routers/flash/AaveV3FlashRouter.sol | 2 +- .../routers/flash/BalancerFlashRouter.sol | 2 +- contracts/routers/flash/BaseFlashRouter.sol | 2 +- .../routers/flash/ERC3156FlashRouter.sol | 2 +- contracts/routers/flash/MorphoFlashRouter.sol | 2 +- contracts/routers/flash/UniV2FlashRouter.sol | 2 +- contracts/routers/flash/UniV3FlashRouter.sol | 2 +- .../ethereum-mainnet/EthereumFlashRouter.sol | 2 +- .../ethereum-mainnet/MakerFlashRouter.sol | 2 +- foundry.toml | 1 + hardhat.config.ts | 3 +- lib/morpho-blue | 2 +- test/forge/helpers/ForkTest.sol | 34 ++++++++++--------- 32 files changed, 88 insertions(+), 46 deletions(-) diff --git a/.gitmodules b/.gitmodules index dc8221d7..91b8221f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,6 +19,7 @@ [submodule "lib/morpho-blue"] path = lib/morpho-blue url = https://github.com/morpho-labs/morpho-blue + branch = 0.8.21 [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate diff --git a/config/ConfigLib.sol b/config/ConfigLib.sol index b8079875..e4c7a7c7 100644 --- a/config/ConfigLib.sol +++ b/config/ConfigLib.sol @@ -7,12 +7,27 @@ struct Config { string json; } +/// @dev Warning: keys must be ordered alphabetically. +struct RawConfigMarket { + string borrowableToken; + address chainlinkFeed; + string collateralToken; + uint256 lltv; +} + +struct ConfigMarket { + address collateralToken; + address borrowableToken; + address chainlinkFeed; +} + library ConfigLib { using stdJson for string; string internal constant CHAIN_ID_PATH = "$.chainId"; string internal constant RPC_ALIAS_PATH = "$.rpcAlias"; string internal constant FORK_BLOCK_NUMBER_PATH = "$.forkBlockNumber"; + string internal constant MARKETS_PATH = "$.markets"; string internal constant WRAPPED_NATIVE_PATH = "$.wrappedNative"; string internal constant LSD_NATIVES_PATH = "$.lsdNatives"; @@ -50,4 +65,21 @@ library ConfigLib { function getLsdNatives(Config storage config) internal returns (address[] memory) { return getAddressArray(config, config.json.readStringArray(LSD_NATIVES_PATH)); } + + function getMarkets(Config storage config) internal returns (ConfigMarket[] memory markets) { + bytes memory encodedMarkets = config.json.parseRaw(MARKETS_PATH); + RawConfigMarket[] memory rawMarkets = abi.decode(encodedMarkets, (RawConfigMarket[])); + + markets = new ConfigMarket[](rawMarkets.length); + + for (uint256 i; i < rawMarkets.length; ++i) { + RawConfigMarket memory rawMarket = rawMarkets[i]; + + markets[i] = ConfigMarket({ + collateralToken: getAddress(config, rawMarket.collateralToken), + borrowableToken: getAddress(config, rawMarket.borrowableToken), + chainlinkFeed: rawMarket.chainlinkFeed + }); + } + } } diff --git a/config/Configured.sol b/config/Configured.sol index f29e14ff..37fa0bc4 100644 --- a/config/Configured.sol +++ b/config/Configured.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import {Config, ConfigLib} from "config/ConfigLib.sol"; +import {Config, ConfigMarket, ConfigLib} from "./ConfigLib.sol"; import {StdChains, VmSafe} from "@forge-std/StdChains.sol"; @@ -22,6 +22,8 @@ abstract contract Configured is StdChains { address[] internal lsdNatives; address[] internal allAssets; + ConfigMarket[] internal configMarkets; + function _network() internal view virtual returns (string memory); function _rpcAlias() internal virtual returns (string memory) { @@ -51,6 +53,8 @@ abstract contract Configured is StdChains { allAssets = [dai, usdc, usdt, link, wbtc, weth]; + configMarkets = config.getMarkets(); + for (uint256 i; i < lsdNatives.length; ++i) { allAssets.push(lsdNatives[i]); } diff --git a/config/ethereum-mainnet.json b/config/ethereum-mainnet.json index 4c20dd00..e93d1c45 100644 --- a/config/ethereum-mainnet.json +++ b/config/ethereum-mainnet.json @@ -4,9 +4,10 @@ "forkBlockNumber": 17891982, "markets": [ { - "collateralAsset": "DAI", - "borrowableAsset": "WETH", - "chainlinkOracle": "0x773616E4d11A78F511299002da57A0a94577F1f4" + "borrowableToken": "WETH", + "collateralToken": "DAI", + "chainlinkFeed": "0x773616E4d11A78F511299002da57A0a94577F1f4", + "lltv": "800000000000000000" } ], "DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", diff --git a/contracts/BaseCallbackReceiver.sol b/contracts/BaseCallbackReceiver.sol index c2cc8127..24308a57 100644 --- a/contracts/BaseCallbackReceiver.sol +++ b/contracts/BaseCallbackReceiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; diff --git a/contracts/BaseSelfMulticall.sol b/contracts/BaseSelfMulticall.sol index 7083cdf5..06fe3259 100644 --- a/contracts/BaseSelfMulticall.sol +++ b/contracts/BaseSelfMulticall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; /// @title BaseSelfMulticall /// @author Morpho Labs diff --git a/contracts/bundlers/BaseBundler.sol b/contracts/bundlers/BaseBundler.sol index af184597..d7089cf8 100644 --- a/contracts/bundlers/BaseBundler.sol +++ b/contracts/bundlers/BaseBundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IMulticall} from "./interfaces/IMulticall.sol"; diff --git a/contracts/bundlers/ERC20Bundler.sol b/contracts/bundlers/ERC20Bundler.sol index 5676430a..cb416061 100644 --- a/contracts/bundlers/ERC20Bundler.sol +++ b/contracts/bundlers/ERC20Bundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {Signature} from "@morpho-blue/interfaces/IMorpho.sol"; diff --git a/contracts/bundlers/ERC4626Bundler.sol b/contracts/bundlers/ERC4626Bundler.sol index dc8e0bee..9951461b 100644 --- a/contracts/bundlers/ERC4626Bundler.sol +++ b/contracts/bundlers/ERC4626Bundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; diff --git a/contracts/bundlers/EVMBundler.sol b/contracts/bundlers/EVMBundler.sol index f2c4be66..1799a90b 100644 --- a/contracts/bundlers/EVMBundler.sol +++ b/contracts/bundlers/EVMBundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {MorphoBundler} from "./MorphoBundler.sol"; import {ERC20Bundler} from "./ERC20Bundler.sol"; diff --git a/contracts/bundlers/MorphoBundler.sol b/contracts/bundlers/MorphoBundler.sol index d3e5265e..a6557718 100644 --- a/contracts/bundlers/MorphoBundler.sol +++ b/contracts/bundlers/MorphoBundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IMorphoBundler} from "./interfaces/IMorphoBundler.sol"; import {MarketParams, Signature, Authorization, IMorpho} from "@morpho-blue/interfaces/IMorpho.sol"; diff --git a/contracts/bundlers/OneInchBundler.sol b/contracts/bundlers/OneInchBundler.sol index 0ba52310..63307d36 100644 --- a/contracts/bundlers/OneInchBundler.sol +++ b/contracts/bundlers/OneInchBundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {I1InchAggregationRouterV5} from "./interfaces/I1InchAggregationRouterV5.sol"; diff --git a/contracts/bundlers/WNativeBundler.sol b/contracts/bundlers/WNativeBundler.sol index 35a7e9dd..20b52862 100644 --- a/contracts/bundlers/WNativeBundler.sol +++ b/contracts/bundlers/WNativeBundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IWNative} from "./interfaces/IWNative.sol"; diff --git a/contracts/bundlers/ethereum-mainnet/EthereumWrapperBundler.sol b/contracts/bundlers/ethereum-mainnet/EthereumWrapperBundler.sol index abfa598d..a067a97f 100644 --- a/contracts/bundlers/ethereum-mainnet/EthereumWrapperBundler.sol +++ b/contracts/bundlers/ethereum-mainnet/EthereumWrapperBundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {ERC4626Bundler} from "../ERC4626Bundler.sol"; import {WNativeBundler} from "../WNativeBundler.sol"; diff --git a/contracts/bundlers/ethereum-mainnet/StEthBundler.sol b/contracts/bundlers/ethereum-mainnet/StEthBundler.sol index 5340f6f0..0b5cf82e 100644 --- a/contracts/bundlers/ethereum-mainnet/StEthBundler.sol +++ b/contracts/bundlers/ethereum-mainnet/StEthBundler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IWStEth} from "./interfaces/IWStEth.sol"; diff --git a/contracts/meta-morpho/InternalSupplyRouter.sol b/contracts/meta-morpho/InternalSupplyRouter.sol index 12d7c67a..0c34fac7 100644 --- a/contracts/meta-morpho/InternalSupplyRouter.sol +++ b/contracts/meta-morpho/InternalSupplyRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IMorpho} from "@morpho-blue/interfaces/IMorpho.sol"; diff --git a/contracts/meta-morpho/SupplyVault.sol b/contracts/meta-morpho/SupplyVault.sol index 77f24e8d..093312d0 100644 --- a/contracts/meta-morpho/SupplyVault.sol +++ b/contracts/meta-morpho/SupplyVault.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {ISupplyVault} from "./interfaces/ISupplyVault.sol"; import {IVaultAllocationManager} from "./interfaces/IVaultAllocationManager.sol"; diff --git a/contracts/routers/flash/AaveFlashRouter.sol b/contracts/routers/flash/AaveFlashRouter.sol index aa87d0b2..981fd229 100644 --- a/contracts/routers/flash/AaveFlashRouter.sol +++ b/contracts/routers/flash/AaveFlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IAaveFlashLender} from "./interfaces/IAaveFlashLender.sol"; import {IAaveFlashBorrower} from "./interfaces/IAaveFlashBorrower.sol"; diff --git a/contracts/routers/flash/AaveV2FlashRouter.sol b/contracts/routers/flash/AaveV2FlashRouter.sol index 836cbd4c..d78eadb1 100644 --- a/contracts/routers/flash/AaveV2FlashRouter.sol +++ b/contracts/routers/flash/AaveV2FlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IAaveFlashLender} from "./interfaces/IAaveFlashLender.sol"; diff --git a/contracts/routers/flash/AaveV3FlashRouter.sol b/contracts/routers/flash/AaveV3FlashRouter.sol index 3d86c951..724d45f0 100644 --- a/contracts/routers/flash/AaveV3FlashRouter.sol +++ b/contracts/routers/flash/AaveV3FlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IAaveFlashLender} from "./interfaces/IAaveFlashLender.sol"; diff --git a/contracts/routers/flash/BalancerFlashRouter.sol b/contracts/routers/flash/BalancerFlashRouter.sol index 9411255e..c77c5050 100644 --- a/contracts/routers/flash/BalancerFlashRouter.sol +++ b/contracts/routers/flash/BalancerFlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IBalancerFlashLender} from "./interfaces/IBalancerFlashLender.sol"; import {IBalancerFlashBorrower} from "./interfaces/IBalancerFlashBorrower.sol"; diff --git a/contracts/routers/flash/BaseFlashRouter.sol b/contracts/routers/flash/BaseFlashRouter.sol index f2e1b788..291abb64 100644 --- a/contracts/routers/flash/BaseFlashRouter.sol +++ b/contracts/routers/flash/BaseFlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IFlashBorrower} from "./interfaces/IFlashBorrower.sol"; diff --git a/contracts/routers/flash/ERC3156FlashRouter.sol b/contracts/routers/flash/ERC3156FlashRouter.sol index 4b6f2a23..88d77972 100644 --- a/contracts/routers/flash/ERC3156FlashRouter.sol +++ b/contracts/routers/flash/ERC3156FlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IERC3156FlashLender} from "./interfaces/IERC3156FlashLender.sol"; import {IERC3156FlashBorrower} from "./interfaces/IERC3156FlashBorrower.sol"; diff --git a/contracts/routers/flash/MorphoFlashRouter.sol b/contracts/routers/flash/MorphoFlashRouter.sol index c6286c92..55f70ac1 100644 --- a/contracts/routers/flash/MorphoFlashRouter.sol +++ b/contracts/routers/flash/MorphoFlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IMorphoFlashLoanCallback} from "@morpho-blue/interfaces/IMorphoCallbacks.sol"; import {IMorpho} from "@morpho-blue/interfaces/IMorpho.sol"; diff --git a/contracts/routers/flash/UniV2FlashRouter.sol b/contracts/routers/flash/UniV2FlashRouter.sol index 3e07a76b..a04d85d4 100644 --- a/contracts/routers/flash/UniV2FlashRouter.sol +++ b/contracts/routers/flash/UniV2FlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IUniV2Factory} from "./interfaces/IUniV2Factory.sol"; import {IUniV2FlashLender} from "./interfaces/IUniV2FlashLender.sol"; diff --git a/contracts/routers/flash/UniV3FlashRouter.sol b/contracts/routers/flash/UniV3FlashRouter.sol index 0d58f13c..fe6b1c92 100644 --- a/contracts/routers/flash/UniV3FlashRouter.sol +++ b/contracts/routers/flash/UniV3FlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IUniV3FlashLender} from "./interfaces/IUniV3FlashLender.sol"; import {IUniV3FlashBorrower} from "./interfaces/IUniV3FlashBorrower.sol"; diff --git a/contracts/routers/flash/ethereum-mainnet/EthereumFlashRouter.sol b/contracts/routers/flash/ethereum-mainnet/EthereumFlashRouter.sol index 18d6bd28..08352256 100644 --- a/contracts/routers/flash/ethereum-mainnet/EthereumFlashRouter.sol +++ b/contracts/routers/flash/ethereum-mainnet/EthereumFlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {BaseFlashRouter} from "../BaseFlashRouter.sol"; import {AaveV2FlashRouter} from "../AaveV2FlashRouter.sol"; diff --git a/contracts/routers/flash/ethereum-mainnet/MakerFlashRouter.sol b/contracts/routers/flash/ethereum-mainnet/MakerFlashRouter.sol index c5d12977..296f094f 100644 --- a/contracts/routers/flash/ethereum-mainnet/MakerFlashRouter.sol +++ b/contracts/routers/flash/ethereum-mainnet/MakerFlashRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.19; +pragma solidity 0.8.21; import {IERC3156FlashLender} from "../interfaces/IERC3156FlashLender.sol"; diff --git a/foundry.toml b/foundry.toml index 0b200e2d..d747124d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,6 +4,7 @@ out = "out" libs = ["lib"] fs_permissions = [{ access = "read", path = "./config/"}] via-ir = true +evm_version = "paris" [invariant] runs = 16 diff --git a/hardhat.config.ts b/hardhat.config.ts index 5f4e924c..3c9234ae 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -28,13 +28,14 @@ const config: HardhatUserConfig = { solidity: { compilers: [ { - version: "0.8.19", + version: "0.8.21", settings: { optimizer: { enabled: true, runs: 200, }, viaIR: true, + evmVersion: "paris", }, }, ], diff --git a/lib/morpho-blue b/lib/morpho-blue index d814c038..4cbf35a6 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit d814c03842537ea54c8d83b02281bcc4ca02f511 +Subproject commit 4cbf35a697392ceaaee972ee6ac3ca27b30da0d2 diff --git a/test/forge/helpers/ForkTest.sol b/test/forge/helpers/ForkTest.sol index f2c334a3..092d4062 100644 --- a/test/forge/helpers/ForkTest.sol +++ b/test/forge/helpers/ForkTest.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import {Config, ConfigLib} from "config/ConfigLib.sol"; - -import {Configured} from "config/Configured.sol"; +import "config/Configured.sol"; import "./BaseTest.sol"; @@ -30,19 +28,23 @@ abstract contract ForkTest is BaseTest, Configured { super.setUp(); - // for (uint256 i; i < allAssets.length; ++i) { - // vm.startPrank(OWNER); - // morpho.createMarket( - // Market({ - // collateralAsset: allAssets[i], - // borrowableAsset: usdc, - // oracle: address(oracle), - // irm: address(irm), - // lltv: LLTV - // }) - // ); - // vm.stopPrank(); - // } + for (uint256 i; i < configMarkets.length; ++i) { + ConfigMarket memory configMarket = configMarkets[i]; + + ChainlinkOracle oracle = new ChainlinkOracle(); + + vm.startPrank(OWNER); + morpho.createMarket( + MarketParams({ + collateralToken: configMarket.collateralToken, + borrowableToken: configMarket.borrowableToken, + oracle: address(oracle), + irm: address(irm), + lltv: configMarket.lltv + }) + ); + vm.stopPrank(); + } } function _fork() internal virtual { From bcaf97e96d32f6addabf1b391685770fc77b7c99 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 22 Aug 2023 12:17:16 +0200 Subject: [PATCH 05/19] refactor(price-scale): use Morpho's fixed price scale --- contracts/oracles/BaseOracle.sol | 10 +++------- contracts/oracles/ChainlinkOracle.sol | 2 +- contracts/oracles/ChainlinkPairOracle.sol | 3 +-- .../oracles/ChainlinkUniswapV3Oracle.sol | 3 +-- .../oracles/UniswapV3ChainlinkOracle.sol | 3 +-- contracts/oracles/UniswapV3Oracle.sol | 5 +---- contracts/oracles/UniswapV3PairOracle.sol | 2 -- test/forge/ChainlinkPairOracle.t.sol | 19 +++++++------------ 8 files changed, 15 insertions(+), 32 deletions(-) diff --git a/contracts/oracles/BaseOracle.sol b/contracts/oracles/BaseOracle.sol index 290f8100..e5124e76 100644 --- a/contracts/oracles/BaseOracle.sol +++ b/contracts/oracles/BaseOracle.sol @@ -5,22 +5,18 @@ import {IOracle} from "./interfaces/IOracle.sol"; import {FullMath} from "@uniswap/v3-core/libraries/FullMath.sol"; +/// @dev The scale as expected by Morpho Blue. +uint256 constant PRICE_SCALE = 1e36; + abstract contract BaseOracle is IOracle { using FullMath for uint256; - /// @dev The scale must be 1e36 * 10^(decimals of borrowable token - decimals of collateral token). - uint256 public immutable PRICE_SCALE; - // @dev The collateral price's unit. uint256 public immutable COLLATERAL_SCALE; // @dev The borrowable price's unit. uint256 public immutable BORROWABLE_SCALE; - constructor(uint256 priceScale) { - PRICE_SCALE = priceScale; - } - function price() external view returns (uint256) { // Using FullMath to avoid overflowing because of PRICE_SCALE. return PRICE_SCALE.mulDiv(collateralPrice() * BORROWABLE_SCALE, borrowablePrice() * COLLATERAL_SCALE); diff --git a/contracts/oracles/ChainlinkOracle.sol b/contracts/oracles/ChainlinkOracle.sol index 29eab8c7..81ab8531 100644 --- a/contracts/oracles/ChainlinkOracle.sol +++ b/contracts/oracles/ChainlinkOracle.sol @@ -6,5 +6,5 @@ import {StaticBorrowableAdapter} from "./adapters/StaticBorrowableAdapter.sol"; import {ChainlinkCollateralAdapter} from "./adapters/ChainlinkCollateralAdapter.sol"; contract ChainlinkOracle is BaseOracle, ChainlinkCollateralAdapter, StaticBorrowableAdapter { - constructor(uint256 priceScale, address feed) BaseOracle(priceScale) ChainlinkCollateralAdapter(feed) {} + constructor(address feed) ChainlinkCollateralAdapter(feed) {} } diff --git a/contracts/oracles/ChainlinkPairOracle.sol b/contracts/oracles/ChainlinkPairOracle.sol index 9b2d7064..9e30fd3e 100644 --- a/contracts/oracles/ChainlinkPairOracle.sol +++ b/contracts/oracles/ChainlinkPairOracle.sol @@ -6,8 +6,7 @@ import {ChainlinkCollateralAdapter} from "./adapters/ChainlinkCollateralAdapter. import {ChainlinkBorrowableAdapter} from "./adapters/ChainlinkBorrowableAdapter.sol"; contract ChainlinkPairOracle is BaseOracle, ChainlinkCollateralAdapter, ChainlinkBorrowableAdapter { - constructor(uint256 priceScale, address collateralFeed, address borrowableFeed) - BaseOracle(priceScale) + constructor(address collateralFeed, address borrowableFeed) ChainlinkCollateralAdapter(collateralFeed) ChainlinkBorrowableAdapter(borrowableFeed) {} diff --git a/contracts/oracles/ChainlinkUniswapV3Oracle.sol b/contracts/oracles/ChainlinkUniswapV3Oracle.sol index 5b8c1fe4..7da31fbf 100644 --- a/contracts/oracles/ChainlinkUniswapV3Oracle.sol +++ b/contracts/oracles/ChainlinkUniswapV3Oracle.sol @@ -6,8 +6,7 @@ import {ChainlinkCollateralAdapter} from "./adapters/ChainlinkCollateralAdapter. import {UniswapV3BorrowableAdapter} from "./adapters/UniswapV3BorrowableAdapter.sol"; contract ChainlinkUniswapV3Oracle is BaseOracle, ChainlinkCollateralAdapter, UniswapV3BorrowableAdapter { - constructor(uint256 priceScale, address feed, address pool, uint32 borrowablePriceDelay) - BaseOracle(priceScale) + constructor(address feed, address pool, uint32 borrowablePriceDelay) ChainlinkCollateralAdapter(feed) UniswapV3BorrowableAdapter(pool, borrowablePriceDelay) {} diff --git a/contracts/oracles/UniswapV3ChainlinkOracle.sol b/contracts/oracles/UniswapV3ChainlinkOracle.sol index 17cdb720..cb04d54d 100644 --- a/contracts/oracles/UniswapV3ChainlinkOracle.sol +++ b/contracts/oracles/UniswapV3ChainlinkOracle.sol @@ -6,8 +6,7 @@ import {UniswapV3CollateralAdapter} from "./adapters/UniswapV3CollateralAdapter. import {ChainlinkBorrowableAdapter} from "./adapters/ChainlinkBorrowableAdapter.sol"; contract UniswapV3ChainlinkOracle is BaseOracle, UniswapV3CollateralAdapter, ChainlinkBorrowableAdapter { - constructor(uint256 priceScale, address pool, address feed, uint32 collateralPriceDelay) - BaseOracle(priceScale) + constructor(address pool, address feed, uint32 collateralPriceDelay) UniswapV3CollateralAdapter(pool, collateralPriceDelay) ChainlinkBorrowableAdapter(feed) {} diff --git a/contracts/oracles/UniswapV3Oracle.sol b/contracts/oracles/UniswapV3Oracle.sol index 20d10214..4d0ea956 100644 --- a/contracts/oracles/UniswapV3Oracle.sol +++ b/contracts/oracles/UniswapV3Oracle.sol @@ -6,8 +6,5 @@ import {StaticBorrowableAdapter} from "./adapters/StaticBorrowableAdapter.sol"; import {UniswapV3CollateralAdapter} from "./adapters/UniswapV3CollateralAdapter.sol"; contract UniswapV3Oracle is BaseOracle, UniswapV3CollateralAdapter, StaticBorrowableAdapter { - constructor(uint256 priceScale, address pool, uint32 delay) - BaseOracle(priceScale) - UniswapV3CollateralAdapter(pool, delay) - {} + constructor(address pool, uint32 delay) UniswapV3CollateralAdapter(pool, delay) {} } diff --git a/contracts/oracles/UniswapV3PairOracle.sol b/contracts/oracles/UniswapV3PairOracle.sol index 5f1b2d32..1614c049 100644 --- a/contracts/oracles/UniswapV3PairOracle.sol +++ b/contracts/oracles/UniswapV3PairOracle.sol @@ -7,13 +7,11 @@ import {UniswapV3BorrowableAdapter} from "./adapters/UniswapV3BorrowableAdapter. contract UniswapV3Oracle is BaseOracle, UniswapV3CollateralAdapter, UniswapV3BorrowableAdapter { constructor( - uint256 priceScale, address collateralPool, address borrowablePool, uint32 collateralPriceDelay, uint32 borrowablePriceDelay ) - BaseOracle(priceScale) UniswapV3CollateralAdapter(collateralPool, collateralPriceDelay) UniswapV3BorrowableAdapter(borrowablePool, borrowablePriceDelay) {} diff --git a/test/forge/ChainlinkPairOracle.t.sol b/test/forge/ChainlinkPairOracle.t.sol index e62e5617..2a6f889a 100644 --- a/test/forge/ChainlinkPairOracle.t.sol +++ b/test/forge/ChainlinkPairOracle.t.sol @@ -5,6 +5,7 @@ import "./mocks/ChainlinkAggregatorV3Mock.sol"; import "./mocks/ERC20Mock.sol"; import "contracts/oracles/ChainlinkPairOracle.sol"; +import {PRICE_SCALE} from "contracts/oracles/BaseOracle.sol"; import {OracleFeed} from "contracts/oracles/libraries/OracleFeed.sol"; import {FullMath} from "@uniswap/v3-core/libraries/FullMath.sol"; @@ -13,12 +14,13 @@ import "@forge-std/console2.sol"; import "@forge-std/Test.sol"; contract ChainlinkOracleTest is Test { + using FullMath for uint256; + ChainlinkAggregatorV3Mock collateralFeed; ChainlinkAggregatorV3Mock borrowableFeed; ChainlinkPairOracle chainlinkOracle; ERC20Mock collateral; ERC20Mock borrowable; - uint256 SCALE; uint8 COLLATERAL_DECIMALS = 8; uint8 BORROWABLE_DECIMALS = 10; @@ -32,9 +34,7 @@ contract ChainlinkOracleTest is Test { collateralFeed.setDecimals(COLLATERAL_DECIMALS); borrowableFeed.setDecimals(BORROWABLE_DECIMALS); - SCALE = 1e26; // 1e36 * 10 ** (8 - 18); - - chainlinkOracle = new ChainlinkPairOracle(SCALE, address(collateralFeed), address(borrowableFeed)); + chainlinkOracle = new ChainlinkPairOracle( address(collateralFeed), address(borrowableFeed)); } function testConfig() public { @@ -47,7 +47,6 @@ contract ChainlinkOracleTest is Test { assertEq(borrowableChainlinkFeed, address(borrowableFeed), "borrowableChainlinkFeed"); assertEq(chainlinkOracle.COLLATERAL_SCALE(), 10 ** COLLATERAL_DECIMALS); assertEq(chainlinkOracle.BORROWABLE_SCALE(), 10 ** BORROWABLE_DECIMALS); - assertEq(chainlinkOracle.PRICE_SCALE(), SCALE); } function testNegativePrice(int256 price) public { @@ -91,16 +90,12 @@ contract ChainlinkOracleTest is Test { collateralFeed.setLatestAnswer(int256(collateralPrice)); borrowableFeed.setLatestAnswer(int256(borrowablePrice)); - uint256 scale = collateralDecimals > borrowableDecimals - ? 1e36 / 10 ** (collateralDecimals - borrowableDecimals) - : 1e36 * (borrowableDecimals - collateralDecimals); // 1e36 * 10 ** (borrow decimals - collateral decimals); - - chainlinkOracle = new ChainlinkPairOracle(scale, address(collateralFeed), address(borrowableFeed)); + chainlinkOracle = new ChainlinkPairOracle( address(collateralFeed), address(borrowableFeed)); assertEq( chainlinkOracle.price(), - FullMath.mulDiv( - collateralPrice * 10 ** borrowableFeedDecimals, scale, borrowablePrice * 10 ** collateralFeedDecimals + PRICE_SCALE.mulDiv( + collateralPrice * 10 ** borrowableFeedDecimals, borrowablePrice * 10 ** collateralFeedDecimals ) ); } From ffc89f145f61dc078116b380380f8283def0175b Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 22 Aug 2023 12:24:54 +0200 Subject: [PATCH 06/19] refactor(fork-test): create config markets --- config/ConfigLib.sol | 4 +++- test/forge/helpers/ForkTest.sol | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/config/ConfigLib.sol b/config/ConfigLib.sol index e4c7a7c7..c8c32268 100644 --- a/config/ConfigLib.sol +++ b/config/ConfigLib.sol @@ -19,6 +19,7 @@ struct ConfigMarket { address collateralToken; address borrowableToken; address chainlinkFeed; + uint256 lltv; } library ConfigLib { @@ -78,7 +79,8 @@ library ConfigLib { markets[i] = ConfigMarket({ collateralToken: getAddress(config, rawMarket.collateralToken), borrowableToken: getAddress(config, rawMarket.borrowableToken), - chainlinkFeed: rawMarket.chainlinkFeed + chainlinkFeed: rawMarket.chainlinkFeed, + lltv: rawMarket.lltv }); } } diff --git a/test/forge/helpers/ForkTest.sol b/test/forge/helpers/ForkTest.sol index 092d4062..cab66fdd 100644 --- a/test/forge/helpers/ForkTest.sol +++ b/test/forge/helpers/ForkTest.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.0; import "config/Configured.sol"; +import {ChainlinkOracle} from "contracts/oracles/ChainlinkOracle.sol"; + import "./BaseTest.sol"; abstract contract ForkTest is BaseTest, Configured { @@ -31,9 +33,10 @@ abstract contract ForkTest is BaseTest, Configured { for (uint256 i; i < configMarkets.length; ++i) { ConfigMarket memory configMarket = configMarkets[i]; - ChainlinkOracle oracle = new ChainlinkOracle(); + ChainlinkOracle oracle = new ChainlinkOracle(configMarket.chainlinkFeed); vm.startPrank(OWNER); + if (!morpho.isLltvEnabled(configMarket.lltv)) morpho.enableLltv(configMarket.lltv); morpho.createMarket( MarketParams({ collateralToken: configMarket.collateralToken, From f3fec1cc8d876680bfb6ccb0ff5779f1315b93ff Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 09:36:03 +0200 Subject: [PATCH 07/19] test(setUp): refactor inheritance --- contracts/meta-morpho/SupplyVault.sol | 4 ++-- lib/morpho-blue | 2 +- remappings.txt | 1 + test/forge/ChainlinkPairOracle.t.sol | 13 ++----------- test/forge/helpers/BaseTest.sol | 9 +++------ test/forge/helpers/LocalTest.sol | 15 +++++++++------ test/forge/mocks/ERC20Mock.sol | 12 ------------ 7 files changed, 18 insertions(+), 38 deletions(-) delete mode 100644 test/forge/mocks/ERC20Mock.sol diff --git a/contracts/meta-morpho/SupplyVault.sol b/contracts/meta-morpho/SupplyVault.sol index 01ea5e3b..6dca1736 100644 --- a/contracts/meta-morpho/SupplyVault.sol +++ b/contracts/meta-morpho/SupplyVault.sol @@ -9,7 +9,7 @@ import {MarketAllocation} from "./libraries/Types.sol"; import {IMorpho, MorphoLib} from "@morpho-blue/libraries/periphery/MorphoLib.sol"; import {UnauthorizedMarket, InconsistentAsset, SupplyCapExceeded} from "./libraries/Errors.sol"; import {MarketConfig, MarketConfigData, ConfigSet, ConfigSetLib} from "./libraries/ConfigSetLib.sol"; -import {Id, MarketParams, MarketLib} from "@morpho-blue/libraries/MarketLib.sol"; +import {Id, MarketParams, MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol"; import {SharesMathLib} from "@morpho-blue/libraries/SharesMathLib.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -20,7 +20,7 @@ contract SupplyVault is ISupplyVault, InternalSupplyRouter, ERC4626, Ownable { using MorphoLib for IMorpho; using SharesMathLib for uint256; using ConfigSetLib for ConfigSet; - using MarketLib for MarketParams; + using MarketParamsLib for MarketParams; address private _riskManager; address private _allocationManager; diff --git a/lib/morpho-blue b/lib/morpho-blue index 4cbf35a6..9f646fd7 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit 4cbf35a697392ceaaee972ee6ac3ca27b30da0d2 +Subproject commit 9f646fd7255ab49d80fc5c7f0648673126359ebe diff --git a/remappings.txt b/remappings.txt index 00871a19..5116bf9a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,6 +4,7 @@ test/=test/ @solmate/=lib/solmate/src/ solmate/=lib/solmate/ +openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/ @forge-std/=lib/morpho-blue/lib/forge-std/src/ @morpho-blue/=lib/morpho-blue/src/ diff --git a/test/forge/ChainlinkPairOracle.t.sol b/test/forge/ChainlinkPairOracle.t.sol index d34a979e..4d25465b 100644 --- a/test/forge/ChainlinkPairOracle.t.sol +++ b/test/forge/ChainlinkPairOracle.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "./mocks/ChainlinkAggregatorV3Mock.sol"; -import "./mocks/ERC20Mock.sol"; +import {ERC20Mock} from "@morpho-blue/mocks/ERC20Mock.sol"; +import {ChainlinkAggregatorV3Mock} from "test/forge/mocks/ChainlinkAggregatorV3Mock.sol"; import "contracts/oracles/ChainlinkPairOracle.sol"; @@ -18,17 +18,12 @@ contract ChainlinkOracleTest is Test { ChainlinkAggregatorV3Mock collateralFeed; ChainlinkAggregatorV3Mock borrowableFeed; ChainlinkPairOracle chainlinkOracle; - ERC20Mock collateral; - ERC20Mock borrowable; uint256 SCALE_FACTOR; uint8 COLLATERAL_DECIMALS = 8; uint8 BORROWABLE_DECIMALS = 10; function setUp() public { - collateral = new ERC20Mock("Collateral", "COL", 18); - borrowable = new ERC20Mock("Borrowable", "BOR", 8); - collateralFeed = new ChainlinkAggregatorV3Mock(); borrowableFeed = new ChainlinkAggregatorV3Mock(); @@ -78,10 +73,6 @@ contract ChainlinkOracleTest is Test { collateralPrice = bound(collateralPrice, 1, 10_000_000); borrowablePrice = bound(borrowablePrice, 1, 10_000_000); - // Create tokens. - collateral = new ERC20Mock("Collateral", "COL", uint8(collateralDecimals)); - borrowable = new ERC20Mock("Borrowable", "BOR", uint8(borrowableDecimals)); - collateralPrice *= 10 ** collateralFeedDecimals; borrowablePrice *= 10 ** borrowableFeedDecimals; diff --git a/test/forge/helpers/BaseTest.sol b/test/forge/helpers/BaseTest.sol index f517d90c..03aee88c 100644 --- a/test/forge/helpers/BaseTest.sol +++ b/test/forge/helpers/BaseTest.sol @@ -3,17 +3,14 @@ pragma solidity ^0.8.0; import {Id, MarketParams, Signature, Authorization, IMorpho} from "@morpho-blue/interfaces/IMorpho.sol"; -import {MarketLib} from "@morpho-blue/libraries/MarketLib.sol"; +import {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol"; import {SharesMathLib} from "@morpho-blue/libraries/SharesMathLib.sol"; import {MathLib, WAD} from "@morpho-blue/libraries/MathLib.sol"; import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MorphoLib} from "@morpho-blue/libraries/periphery/MorphoLib.sol"; import {MorphoBalancesLib} from "@morpho-blue/libraries/periphery/MorphoBalancesLib.sol"; -import {Morpho, AUTHORIZATION_TYPEHASH} from "@morpho-blue/Morpho.sol"; -import {ERC20Mock} from "test/forge/mocks/ERC20Mock.sol"; -import {OracleMock} from "@morpho-blue/mocks/OracleMock.sol"; +import {Morpho} from "@morpho-blue/Morpho.sol"; import {IrmMock} from "@morpho-blue/mocks/IrmMock.sol"; import "@forge-std/Test.sol"; @@ -22,7 +19,7 @@ import "@forge-std/console2.sol"; abstract contract BaseTest is Test { using MathLib for uint256; using SharesMathLib for uint256; - using MarketLib for MarketParams; + using MarketParamsLib for MarketParams; using SafeTransferLib for ERC20; using stdStorage for StdStorage; diff --git a/test/forge/helpers/LocalTest.sol b/test/forge/helpers/LocalTest.sol index 1d341cdb..f492c1e8 100644 --- a/test/forge/helpers/LocalTest.sol +++ b/test/forge/helpers/LocalTest.sol @@ -1,21 +1,24 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {ERC20Mock} from "test/forge/mocks/ERC20Mock.sol"; +import {IOracle} from "@morpho-blue/interfaces/IOracle.sol"; + +import {ERC20Mock} from "@morpho-blue/mocks/ERC20Mock.sol"; +import {OracleMock} from "@morpho-blue/mocks/OracleMock.sol"; import "./BaseTest.sol"; abstract contract LocalTest is BaseTest { using MathLib for uint256; using SharesMathLib for uint256; - using MarketLib for MarketParams; + using MarketParamsLib for MarketParams; using stdStorage for StdStorage; uint256 internal constant LLTV = 0.8 ether; ERC20Mock internal borrowableAsset; ERC20Mock internal collateralAsset; - OracleMock internal oracle; + IOracle internal oracle; MarketParams internal marketParams; Id internal id; @@ -24,8 +27,8 @@ abstract contract LocalTest is BaseTest { super.setUp(); // List a marketParams. - borrowableAsset = new ERC20Mock("borrowable", "B", 18); - collateralAsset = new ERC20Mock("collateral", "C", 18); + borrowableAsset = new ERC20Mock("borrowable", "B"); + collateralAsset = new ERC20Mock("collateral", "C"); oracle = new OracleMock(); irm = new IrmMock(morpho); @@ -34,7 +37,7 @@ abstract contract LocalTest is BaseTest { MarketParams(address(borrowableAsset), address(collateralAsset), address(oracle), address(irm), LLTV); id = marketParams.id(); - oracle.setPrice(ORACLE_PRICE_SCALE); + OracleMock(address(oracle)).setPrice(ORACLE_PRICE_SCALE); vm.startPrank(OWNER); morpho.enableLltv(LLTV); diff --git a/test/forge/mocks/ERC20Mock.sol b/test/forge/mocks/ERC20Mock.sol deleted file mode 100644 index c47786f2..00000000 --- a/test/forge/mocks/ERC20Mock.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import {ERC20} from "@solmate/tokens/ERC20.sol"; - -contract ERC20Mock is ERC20 { - constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) {} - - function setBalance(address owner, uint256 amount) external { - balanceOf[owner] = amount; - } -} From f8a17919b91455c6663b85dcb5b8def794b78120 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 09:56:55 +0200 Subject: [PATCH 08/19] test(base): deploy Morpho using nested compilation --- Makefile | 5 ++++- config/ethereum-mainnet.json | 2 +- foundry.toml | 2 +- lib/morpho-blue | 2 +- test/forge/helpers/BaseTest.sol | 16 +++++++++++++--- test/forge/helpers/SigUtils.sol | 7 +++++-- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 26de9d5f..b88b2f6a 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,9 @@ install: foundryup forge install +blue: + cd lib/morpho-blue/ && FOUNDRY_TEST=/dev/null FOUNDRY_SCRIPT=/dev/null forge build --via-ir + contracts: FOUNDRY_TEST=/dev/null FOUNDRY_SCRIPT=/dev/null forge build --via-ir --extra-output-files irOptimized --sizes --force @@ -19,7 +22,7 @@ test-mainnet: test-local: @FOUNDRY_MATCH_CONTRACT=LocalTest make test -test: +test: blue forge test -vvv diff --git a/config/ethereum-mainnet.json b/config/ethereum-mainnet.json index e93d1c45..5912840d 100644 --- a/config/ethereum-mainnet.json +++ b/config/ethereum-mainnet.json @@ -7,7 +7,7 @@ "borrowableToken": "WETH", "collateralToken": "DAI", "chainlinkFeed": "0x773616E4d11A78F511299002da57A0a94577F1f4", - "lltv": "800000000000000000" + "lltv": 800000000000000000 } ], "DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", diff --git a/foundry.toml b/foundry.toml index 9b49655e..d34eee29 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,7 @@ src = "contracts" out = "out" libs = ["lib"] -fs_permissions = [{ access = "read", path = "./config/"}] +fs_permissions = [{ access = "read", path = "./config/"}, { access = "read", path = "./lib/morpho-blue/out/"}] via-ir = true evm_version = "paris" diff --git a/lib/morpho-blue b/lib/morpho-blue index 9f646fd7..590678de 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit 9f646fd7255ab49d80fc5c7f0648673126359ebe +Subproject commit 590678dec844e290d58bdce83c450f70027aed1d diff --git a/test/forge/helpers/BaseTest.sol b/test/forge/helpers/BaseTest.sol index 03aee88c..0b76687c 100644 --- a/test/forge/helpers/BaseTest.sol +++ b/test/forge/helpers/BaseTest.sol @@ -10,7 +10,6 @@ import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol"; import {MorphoLib} from "@morpho-blue/libraries/periphery/MorphoLib.sol"; import {MorphoBalancesLib} from "@morpho-blue/libraries/periphery/MorphoBalancesLib.sol"; -import {Morpho} from "@morpho-blue/Morpho.sol"; import {IrmMock} from "@morpho-blue/mocks/IrmMock.sol"; import "@forge-std/Test.sol"; @@ -21,7 +20,7 @@ abstract contract BaseTest is Test { using SharesMathLib for uint256; using MarketParamsLib for MarketParams; using SafeTransferLib for ERC20; - using stdStorage for StdStorage; + using stdJson for string; uint256 internal constant MIN_AMOUNT = 1000; uint256 internal constant MAX_AMOUNT = 2 ** 64; @@ -35,11 +34,22 @@ abstract contract BaseTest is Test { IrmMock internal irm; function setUp() public virtual { - morpho = IMorpho(address(new Morpho(OWNER))); + morpho = IMorpho(_deploy("lib/morpho-blue/out/Morpho.sol/Morpho.json", abi.encode(OWNER))); irm = new IrmMock(morpho); vm.prank(OWNER); morpho.enableIrm(address(irm)); } + + function _deploy(string memory artifactPath, bytes memory constructorArgs) internal returns (address deployed) { + string memory artifact = vm.readFile(artifactPath); + bytes memory bytecode = bytes.concat(artifact.readBytes("$.bytecode.object"), constructorArgs); + + assembly { + deployed := create(0, add(bytecode, 0x20), mload(bytecode)) + } + + require(deployed != address(0), string.concat("could not deploy `", artifactPath, "`")); + } } diff --git a/test/forge/helpers/SigUtils.sol b/test/forge/helpers/SigUtils.sol index 45f62c7f..732145b1 100644 --- a/test/forge/helpers/SigUtils.sol +++ b/test/forge/helpers/SigUtils.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {Authorization, AUTHORIZATION_TYPEHASH} from "@morpho-blue/Morpho.sol"; +import {Authorization} from "@morpho-blue/interfaces/IMorpho.sol"; + +bytes32 constant AUTHORIZATION_TYPEHASH = + keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)"); library SigUtils { /// @dev Computes the hash of the EIP-712 encoded data. @@ -25,4 +28,4 @@ library SigUtils { ) ); } -} \ No newline at end of file +} From 5884743376cca6f933a9ff1c91181eeef61c0ec0 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 10:07:15 +0200 Subject: [PATCH 09/19] ci(foundry): compile Morpho --- .github/workflows/foundry.yml | 8 ++++---- test/forge/EVMBundler.t.sol | 2 -- .../{EVMBundler.t.sol => EthereumBundler.t.sol} | 6 ++---- test/forge/helpers/BaseTest.sol | 4 ++++ 4 files changed, 10 insertions(+), 10 deletions(-) rename test/forge/ethereum-mainnet/{EVMBundler.t.sol => EthereumBundler.t.sol} (92%) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 4b998c8e..5fefe195 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -26,8 +26,8 @@ jobs: - type: "fast" fuzz-runs: 256 max-test-rejects: 65536 - invariant-runs: 256 - invariant-depth: 15 + invariant-runs: 16 + invariant-depth: 256 runs-on: ubuntu-latest steps: @@ -41,14 +41,14 @@ jobs: - name: Checkout uses: actions/checkout@v3 with: - token: ${{ steps.generate-token.outputs.token }} + token: ${{ steps.generate-token.outputs.token }} submodules: recursive - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - name: Run Forge tests in ${{ matrix.type }} mode - run: forge test -vvv + run: make test env: FOUNDRY_FUZZ_RUNS: ${{ matrix.fuzz-runs }} FOUNDRY_FUZZ_MAX_TEST_REJECTS: ${{ matrix.max-test-rejects }} diff --git a/test/forge/EVMBundler.t.sol b/test/forge/EVMBundler.t.sol index 2d7b0e0f..0a29b9ff 100644 --- a/test/forge/EVMBundler.t.sol +++ b/test/forge/EVMBundler.t.sol @@ -23,8 +23,6 @@ contract EVMBundlerLocalTest is LocalTest { borrowableAsset.approve(address(bundler), type(uint256).max); collateralAsset.approve(address(bundler), type(uint256).max); morpho.setAuthorization(address(bundler), true); - // So tests can borrow/withdraw on behalf of USER without pranking it. - morpho.setAuthorization(address(this), true); vm.stopPrank(); } diff --git a/test/forge/ethereum-mainnet/EVMBundler.t.sol b/test/forge/ethereum-mainnet/EthereumBundler.t.sol similarity index 92% rename from test/forge/ethereum-mainnet/EVMBundler.t.sol rename to test/forge/ethereum-mainnet/EthereumBundler.t.sol index 59e6991f..e5af5e66 100644 --- a/test/forge/ethereum-mainnet/EVMBundler.t.sol +++ b/test/forge/ethereum-mainnet/EthereumBundler.t.sol @@ -5,7 +5,7 @@ import "contracts/bundlers/EVMBundler.sol"; import "../helpers/ForkTest.sol"; -contract EVMBundlerEthereumTest is ForkTest { +contract EthereumBundlerTest is ForkTest { using MathLib for uint256; using MorphoBalancesLib for IMorpho; @@ -20,10 +20,8 @@ contract EVMBundlerEthereumTest is ForkTest { bundler = new EVMBundler(address(morpho)); - vm.startPrank(USER); + vm.prank(USER); morpho.setAuthorization(address(bundler), true); - morpho.setAuthorization(address(this), true); // So tests can borrow/withdraw on behalf of USER without pranking it. - vm.stopPrank(); } /* INVARIANTS */ diff --git a/test/forge/helpers/BaseTest.sol b/test/forge/helpers/BaseTest.sol index 0b76687c..dc0f0396 100644 --- a/test/forge/helpers/BaseTest.sol +++ b/test/forge/helpers/BaseTest.sol @@ -40,6 +40,10 @@ abstract contract BaseTest is Test { vm.prank(OWNER); morpho.enableIrm(address(irm)); + + // So tests can borrow/withdraw on behalf of USER without pranking it. + vm.prank(USER); + morpho.setAuthorization(address(this), true); } function _deploy(string memory artifactPath, bytes memory constructorArgs) internal returns (address deployed) { From d987bdbabde7eceea49ccb3c7d826594f3ab6076 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 10:15:11 +0200 Subject: [PATCH 10/19] ci(foundry): add debug arg --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b88b2f6a..cfd4db37 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ test-local: @FOUNDRY_MATCH_CONTRACT=LocalTest make test test: blue - forge test -vvv + forge test -vvvvv test-%: From bfaac012f7a10312182f8cf694f007de2b6f3fac Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 10:42:55 +0200 Subject: [PATCH 11/19] test(invariant): remove invariants --- test/forge/EVMBundler.t.sol | 15 --------------- .../ethereum-mainnet/EthereumBundler.t.sol | 18 ------------------ 2 files changed, 33 deletions(-) diff --git a/test/forge/EVMBundler.t.sol b/test/forge/EVMBundler.t.sol index 0a29b9ff..c28c7228 100644 --- a/test/forge/EVMBundler.t.sol +++ b/test/forge/EVMBundler.t.sol @@ -26,21 +26,6 @@ contract EVMBundlerLocalTest is LocalTest { vm.stopPrank(); } - /* INVARIANTS */ - - function invariantBundlerBalanceOfZero() public { - assertEq(collateralAsset.balanceOf(address(bundler)), 0, "collateral.balanceOf(bundler)"); - assertEq(borrowableAsset.balanceOf(address(bundler)), 0, "borrowable.balanceOf(bundler)"); - } - - function invariantBundlerPositionZero() public { - assertEq(morpho.collateral(id, address(bundler)), 0, "collateral(bundler)"); - assertEq(morpho.supplyShares(id, address(bundler)), 0, "supplyShares(bundler)"); - assertEq(morpho.borrowShares(id, address(bundler)), 0, "borrowShares(bundler)"); - } - - /* TESTS */ - function testSetAuthorization(uint256 privateKey, uint32 deadline) public { privateKey = bound(privateKey, 1, type(uint32).max); deadline = uint32(bound(deadline, block.timestamp + 1, type(uint32).max)); diff --git a/test/forge/ethereum-mainnet/EthereumBundler.t.sol b/test/forge/ethereum-mainnet/EthereumBundler.t.sol index e5af5e66..ab7093d4 100644 --- a/test/forge/ethereum-mainnet/EthereumBundler.t.sol +++ b/test/forge/ethereum-mainnet/EthereumBundler.t.sol @@ -24,24 +24,6 @@ contract EthereumBundlerTest is ForkTest { morpho.setAuthorization(address(bundler), true); } - /* INVARIANTS */ - - function invariantBundlerBalanceOfZero() public { - for (uint256 i; i < allAssets.length; ++i) { - ERC20 asset = ERC20(allAssets[i]); - - assertEq(asset.balanceOf(address(bundler)), 0, "asset.balanceOf(bundler)"); - } - } - - function invariantBundlerPositionZero() public { - // assertEq(morpho.collateral(id, address(bundler)), 0, "collateral(bundler)"); - // assertEq(morpho.supplyShares(id, address(bundler)), 0, "supplyShares(bundler)"); - // assertEq(morpho.borrowShares(id, address(bundler)), 0, "borrowShares(bundler)"); - } - - /* TESTS */ - function testSupplyWithPermit2(uint256 amount, address onBehalf) public { vm.assume(onBehalf != address(0)); vm.assume(onBehalf != address(morpho)); From 576c796200c5748db73da33f0acd443262fd1d10 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 10:48:32 +0200 Subject: [PATCH 12/19] ci(forge): add specific test to ci --- .github/workflows/foundry.yml | 7 ++++--- Makefile | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 5fefe195..7859a552 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -21,10 +21,10 @@ jobs: - type: "slow" fuzz-runs: 100000 max-test-rejects: 500000 - invariant-runs: 1000 - invariant-depth: 100 + invariant-runs: 64 + invariant-depth: 1024 - type: "fast" - fuzz-runs: 256 + fuzz-runs: 512 max-test-rejects: 65536 invariant-runs: 16 invariant-depth: 256 @@ -50,6 +50,7 @@ jobs: - name: Run Forge tests in ${{ matrix.type }} mode run: make test env: + ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} FOUNDRY_FUZZ_RUNS: ${{ matrix.fuzz-runs }} FOUNDRY_FUZZ_MAX_TEST_REJECTS: ${{ matrix.max-test-rejects }} FOUNDRY_INVARIANT_RUNS: ${{ matrix.invariant-runs }} diff --git a/Makefile b/Makefile index cfd4db37..b88b2f6a 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ test-local: @FOUNDRY_MATCH_CONTRACT=LocalTest make test test: blue - forge test -vvvvv + forge test -vvv test-%: From e6c67bd41665da685d59f6def5f99fd485ea1548 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 14:20:13 +0200 Subject: [PATCH 13/19] test(ethereum): add permit2 test --- .github/workflows/foundry.yml | 81 +++++++++++++- foundry.toml | 3 + test/forge/EVMBundler.t.sol | 56 +++++----- .../ethereum-mainnet/EthereumBundler.t.sol | 101 ++++++++++++------ test/forge/helpers/BaseTest.sol | 4 +- test/forge/helpers/ForkTest.sol | 30 ++++-- test/forge/helpers/LocalTest.sol | 16 +-- 7 files changed, 208 insertions(+), 83 deletions(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 7859a552..8309b9b5 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -12,7 +12,34 @@ concurrency: cancel-in-progress: true jobs: - forge-test: + build-via-ir: + name: Compilation (via IR) + runs-on: ubuntu-latest + + steps: + - name: Generate a token + id: generate-token + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Checkout + uses: actions/checkout@v3 + with: + token: ${{ steps.generate-token.outputs.token }} + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Build contracts via IR & check sizes + run: make contracts # don't use compilation cache + + test-local: + name: Local tests + runs-on: ubuntu-latest + strategy: fail-fast: true matrix: @@ -24,12 +51,57 @@ jobs: invariant-runs: 64 invariant-depth: 1024 - type: "fast" - fuzz-runs: 512 + fuzz-runs: 256 max-test-rejects: 65536 invariant-runs: 16 invariant-depth: 256 + steps: + - name: Generate a token + id: generate-token + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Checkout + uses: actions/checkout@v3 + with: + token: ${{ steps.generate-token.outputs.token }} + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run local tests in ${{ matrix.type }} mode + run: make test-local + env: + FOUNDRY_FUZZ_RUNS: ${{ matrix.fuzz-runs }} + FOUNDRY_FUZZ_MAX_TEST_REJECTS: ${{ matrix.max-test-rejects }} + FOUNDRY_INVARIANT_RUNS: ${{ matrix.invariant-runs }} + FOUNDRY_INVARIANT_DEPTH: ${{ matrix.invariant-depth }} + FOUNDRY_FUZZ_SEED: 0x${{ github.event.pull_request.base.sha || github.sha }} + + test-mainnet: + name: Ethereum tests runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + type: ["slow", "fast"] + include: + - type: "slow" + fuzz-runs: 1024 + max-test-rejects: 65536 + invariant-runs: 64 + invariant-depth: 1024 + - type: "fast" + fuzz-runs: 256 + max-test-rejects: 65536 + invariant-runs: 16 + invariant-depth: 256 + steps: - name: Generate a token id: generate-token @@ -47,11 +119,12 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - - name: Run Forge tests in ${{ matrix.type }} mode - run: make test + - name: Run mainnet tests in ${{ matrix.type }} mode + run: make test-mainnet env: ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} FOUNDRY_FUZZ_RUNS: ${{ matrix.fuzz-runs }} FOUNDRY_FUZZ_MAX_TEST_REJECTS: ${{ matrix.max-test-rejects }} FOUNDRY_INVARIANT_RUNS: ${{ matrix.invariant-runs }} FOUNDRY_INVARIANT_DEPTH: ${{ matrix.invariant-depth }} + FOUNDRY_FUZZ_SEED: 0x${{ github.event.pull_request.base.sha || github.sha }} diff --git a/foundry.toml b/foundry.toml index d34eee29..c7cacaa9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,6 +6,9 @@ fs_permissions = [{ access = "read", path = "./config/"}, { access = "read", pat via-ir = true evm_version = "paris" +[fuzz] +runs = 32 + [invariant] runs = 16 depth = 4 diff --git a/test/forge/EVMBundler.t.sol b/test/forge/EVMBundler.t.sol index c28c7228..10016263 100644 --- a/test/forge/EVMBundler.t.sol +++ b/test/forge/EVMBundler.t.sol @@ -20,8 +20,8 @@ contract EVMBundlerLocalTest is LocalTest { bundler = new EVMBundler(address(morpho)); vm.startPrank(USER); - borrowableAsset.approve(address(bundler), type(uint256).max); - collateralAsset.approve(address(bundler), type(uint256).max); + borrowableToken.approve(address(bundler), type(uint256).max); + collateralToken.approve(address(bundler), type(uint256).max); morpho.setAuthorization(address(bundler), true); vm.stopPrank(); } @@ -61,19 +61,19 @@ contract EVMBundlerLocalTest is LocalTest { amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); bytes[] memory data = new bytes[](2); - data[0] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(borrowableAsset), amount)); + data[0] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(borrowableToken), amount)); data[1] = abi.encodeCall(MorphoBundler.morphoSupply, (marketParams, amount, 0, onBehalf, hex"")); - borrowableAsset.setBalance(USER, amount); + borrowableToken.setBalance(USER, amount); vm.prank(USER); bundler.multicall(block.timestamp, data); - assertEq(collateralAsset.balanceOf(USER), 0, "collateral.balanceOf(USER)"); - assertEq(borrowableAsset.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); + assertEq(collateralToken.balanceOf(USER), 0, "collateral.balanceOf(USER)"); + assertEq(borrowableToken.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); - assertEq(collateralAsset.balanceOf(onBehalf), 0, "collateral.balanceOf(onBehalf)"); - assertEq(borrowableAsset.balanceOf(onBehalf), 0, "borrowable.balanceOf(onBehalf)"); + assertEq(collateralToken.balanceOf(onBehalf), 0, "collateral.balanceOf(onBehalf)"); + assertEq(borrowableToken.balanceOf(onBehalf), 0, "borrowable.balanceOf(onBehalf)"); assertEq(morpho.collateral(id, onBehalf), 0, "collateral(onBehalf)"); assertEq(morpho.supplyShares(id, onBehalf), amount * SharesMathLib.VIRTUAL_SHARES, "supplyShares(onBehalf)"); @@ -87,11 +87,11 @@ contract EVMBundlerLocalTest is LocalTest { } function _testSupplyCollateralBorrow(uint256 amount, uint256 collateralAmount, address receiver) internal { - assertEq(collateralAsset.balanceOf(USER), 0, "collateral.balanceOf(USER)"); - assertEq(borrowableAsset.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); + assertEq(collateralToken.balanceOf(USER), 0, "collateral.balanceOf(USER)"); + assertEq(borrowableToken.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); - assertEq(collateralAsset.balanceOf(receiver), 0, "collateral.balanceOf(receiver)"); - assertEq(borrowableAsset.balanceOf(receiver), amount, "borrowable.balanceOf(receiver)"); + assertEq(collateralToken.balanceOf(receiver), 0, "collateral.balanceOf(receiver)"); + assertEq(borrowableToken.balanceOf(receiver), amount, "borrowable.balanceOf(receiver)"); assertEq(morpho.collateral(id, USER), collateralAmount, "collateral(USER)"); assertEq(morpho.supplyShares(id, USER), 0, "supplyShares(USER)"); @@ -110,17 +110,17 @@ contract EVMBundlerLocalTest is LocalTest { amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); - borrowableAsset.setBalance(address(this), amount); + borrowableToken.setBalance(address(this), amount); morpho.supply(marketParams, amount, 0, SUPPLIER, hex""); uint256 collateralAmount = amount.wDivUp(LLTV); bytes[] memory data = new bytes[](3); - data[0] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(collateralAsset), collateralAmount)); + data[0] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(collateralToken), collateralAmount)); data[1] = abi.encodeCall(MorphoBundler.morphoSupplyCollateral, (marketParams, collateralAmount, USER, hex"")); data[2] = abi.encodeCall(MorphoBundler.morphoBorrow, (marketParams, amount, 0, receiver)); - collateralAsset.setBalance(USER, collateralAmount); + collateralToken.setBalance(USER, collateralAmount); vm.prank(USER); bundler.multicall(block.timestamp, data); @@ -134,21 +134,21 @@ contract EVMBundlerLocalTest is LocalTest { amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); - borrowableAsset.setBalance(address(this), amount); + borrowableToken.setBalance(address(this), amount); morpho.supply(marketParams, amount, 0, SUPPLIER, hex""); uint256 collateralAmount = amount.wDivUp(LLTV); bytes[] memory callbackData = new bytes[](2); callbackData[0] = abi.encodeCall(MorphoBundler.morphoBorrow, (marketParams, amount, 0, receiver)); - callbackData[1] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(collateralAsset), collateralAmount)); + callbackData[1] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(collateralToken), collateralAmount)); bytes[] memory data = new bytes[](1); data[0] = abi.encodeCall( MorphoBundler.morphoSupplyCollateral, (marketParams, collateralAmount, USER, abi.encode(callbackData)) ); - collateralAsset.setBalance(USER, collateralAmount); + collateralToken.setBalance(USER, collateralAmount); vm.prank(USER); bundler.multicall(block.timestamp, data); @@ -157,11 +157,11 @@ contract EVMBundlerLocalTest is LocalTest { } function _testRepayWithdrawCollateral(uint256 collateralAmount, address receiver) internal { - assertEq(collateralAsset.balanceOf(USER), 0, "collateral.balanceOf(USER)"); - assertEq(borrowableAsset.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); + assertEq(collateralToken.balanceOf(USER), 0, "collateral.balanceOf(USER)"); + assertEq(borrowableToken.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); - assertEq(collateralAsset.balanceOf(receiver), collateralAmount, "collateral.balanceOf(receiver)"); - assertEq(borrowableAsset.balanceOf(receiver), 0, "borrowable.balanceOf(receiver)"); + assertEq(collateralToken.balanceOf(receiver), collateralAmount, "collateral.balanceOf(receiver)"); + assertEq(borrowableToken.balanceOf(receiver), 0, "borrowable.balanceOf(receiver)"); assertEq(morpho.collateral(id, USER), 0, "collateral(USER)"); assertEq(morpho.supplyShares(id, USER), 0, "supplyShares(USER)"); @@ -180,17 +180,17 @@ contract EVMBundlerLocalTest is LocalTest { amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); - borrowableAsset.setBalance(address(this), amount); + borrowableToken.setBalance(address(this), amount); morpho.supply(marketParams, amount, 0, SUPPLIER, hex""); uint256 collateralAmount = amount.wDivUp(LLTV); - collateralAsset.setBalance(address(this), collateralAmount); + collateralToken.setBalance(address(this), collateralAmount); morpho.supplyCollateral(marketParams, collateralAmount, USER, hex""); morpho.borrow(marketParams, amount, 0, USER, USER); bytes[] memory data = new bytes[](3); - data[0] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(borrowableAsset), amount)); + data[0] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(borrowableToken), amount)); data[1] = abi.encodeCall(MorphoBundler.morphoRepay, (marketParams, amount, 0, USER, hex"")); data[2] = abi.encodeCall(MorphoBundler.morphoWithdrawCollateral, (marketParams, collateralAmount, receiver)); @@ -206,19 +206,19 @@ contract EVMBundlerLocalTest is LocalTest { amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); - borrowableAsset.setBalance(address(this), amount); + borrowableToken.setBalance(address(this), amount); morpho.supply(marketParams, amount, 0, SUPPLIER, hex""); uint256 collateralAmount = amount.wDivUp(LLTV); - collateralAsset.setBalance(address(this), collateralAmount); + collateralToken.setBalance(address(this), collateralAmount); morpho.supplyCollateral(marketParams, collateralAmount, USER, hex""); morpho.borrow(marketParams, amount, 0, USER, USER); bytes[] memory callbackData = new bytes[](2); callbackData[0] = abi.encodeCall(MorphoBundler.morphoWithdrawCollateral, (marketParams, collateralAmount, receiver)); - callbackData[1] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(borrowableAsset), amount)); + callbackData[1] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(borrowableToken), amount)); bytes[] memory data = new bytes[](1); data[0] = abi.encodeCall(MorphoBundler.morphoRepay, (marketParams, amount, 0, USER, abi.encode(callbackData))); diff --git a/test/forge/ethereum-mainnet/EthereumBundler.t.sol b/test/forge/ethereum-mainnet/EthereumBundler.t.sol index ab7093d4..670d22c7 100644 --- a/test/forge/ethereum-mainnet/EthereumBundler.t.sol +++ b/test/forge/ethereum-mainnet/EthereumBundler.t.sol @@ -1,15 +1,22 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "contracts/bundlers/EVMBundler.sol"; +import {IAllowanceTransfer} from "@permit2/interfaces/IAllowanceTransfer.sol"; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +import "contracts/bundlers/ethereum-mainnet/EthereumBundler.sol"; import "../helpers/ForkTest.sol"; -contract EthereumBundlerTest is ForkTest { +contract EthereumBundlerEthereumTest is ForkTest { using MathLib for uint256; + using MorphoLib for IMorpho; using MorphoBalancesLib for IMorpho; + using MarketParamsLib for MarketParams; + using SafeTransferLib for ERC20; - EVMBundler private bundler; + EthereumBundler private bundler; function _network() internal pure override returns (string memory) { return "ethereum-mainnet"; @@ -18,42 +25,76 @@ contract EthereumBundlerTest is ForkTest { function setUp() public override { super.setUp(); - bundler = new EVMBundler(address(morpho)); + bundler = new EthereumBundler(address(morpho)); vm.prank(USER); morpho.setAuthorization(address(bundler), true); } - function testSupplyWithPermit2(uint256 amount, address onBehalf) public { + function testSupplyWithPermit2(uint256 seed, uint256 amount, address onBehalf, uint256 privateKey, uint256 deadline) + public + { vm.assume(onBehalf != address(0)); vm.assume(onBehalf != address(morpho)); vm.assume(onBehalf != address(bundler)); amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); - - // bytes[] memory data = new bytes[](2); - // data[0] = abi.encodeCall(ERC20Bundler.transferFrom2, (address(borrowableAsset), amount)); - // data[1] = abi.encodeCall(MorphoBundler.morphoSupply, (market, amount, 0, onBehalf, hex"")); - - // borrowableAsset.setBalance(USER, amount); - - // vm.prank(USER); - // bundler.multicall(block.timestamp, data); - - // assertEq(collateralAsset.balanceOf(USER), 0, "collateral.balanceOf(USER)"); - // assertEq(borrowableAsset.balanceOf(USER), 0, "borrowable.balanceOf(USER)"); - - // assertEq(collateralAsset.balanceOf(onBehalf), 0, "collateral.balanceOf(onBehalf)"); - // assertEq(borrowableAsset.balanceOf(onBehalf), 0, "borrowable.balanceOf(onBehalf)"); - - // assertEq(morpho.collateral(id, onBehalf), 0, "collateral(onBehalf)"); - // assertEq(morpho.supplyShares(id, onBehalf), amount * SharesMathLib.VIRTUAL_SHARES, "supplyShares(onBehalf)"); - // assertEq(morpho.borrowShares(id, onBehalf), 0, "borrowShares(onBehalf)"); - - // if (onBehalf != USER) { - // assertEq(morpho.collateral(id, USER), 0, "collateral(USER)"); - // assertEq(morpho.supplyShares(id, USER), 0, "supplyShares(USER)"); - // assertEq(morpho.borrowShares(id, USER), 0, "borrowShares(USER)"); - // } + privateKey = bound(privateKey, 1, type(uint160).max); + deadline = bound(deadline, block.timestamp, type(uint48).max); + + address user = vm.addr(privateKey); + MarketParams memory marketParams = _randomMarketParams(seed); + + (,, uint48 nonce) = Permit2Lib.PERMIT2.allowance(user, marketParams.borrowableToken, address(bundler)); + bytes32 hashed = ECDSA.toTypedDataHash( + Permit2Lib.PERMIT2.DOMAIN_SEPARATOR(), + PermitHash.hash( + IAllowanceTransfer.PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: marketParams.borrowableToken, + amount: uint160(amount), + expiration: type(uint48).max, + nonce: nonce + }), + spender: address(bundler), + sigDeadline: deadline + }) + ) + ); + + Signature memory signature; + (signature.v, signature.r, signature.s) = vm.sign(privateKey, hashed); + + bytes[] memory data = new bytes[](3); + data[0] = abi.encodeCall(ERC20Bundler.approve2, (marketParams.borrowableToken, amount, deadline, signature)); + data[1] = abi.encodeCall(ERC20Bundler.transferFrom2, (marketParams.borrowableToken, amount)); + data[2] = abi.encodeCall(MorphoBundler.morphoSupply, (marketParams, amount, 0, onBehalf, hex"")); + + _deal(marketParams.borrowableToken, user, amount); + + vm.startPrank(user); + ERC20(marketParams.borrowableToken).safeApprove(address(Permit2Lib.PERMIT2), type(uint256).max); + ERC20(marketParams.collateralToken).safeApprove(address(Permit2Lib.PERMIT2), type(uint256).max); + + bundler.multicall(deadline, data); + vm.stopPrank(); + + assertEq(ERC20(marketParams.collateralToken).balanceOf(user), 0, "collateral.balanceOf(user)"); + assertEq(ERC20(marketParams.borrowableToken).balanceOf(user), 0, "borrowable.balanceOf(user)"); + + assertEq(ERC20(marketParams.collateralToken).balanceOf(onBehalf), 0, "collateral.balanceOf(onBehalf)"); + assertEq(ERC20(marketParams.borrowableToken).balanceOf(onBehalf), 0, "borrowable.balanceOf(onBehalf)"); + + Id id = marketParams.id(); + + assertEq(morpho.collateral(id, onBehalf), 0, "collateral(onBehalf)"); + assertEq(morpho.supplyShares(id, onBehalf), amount * SharesMathLib.VIRTUAL_SHARES, "supplyShares(onBehalf)"); + assertEq(morpho.borrowShares(id, onBehalf), 0, "borrowShares(onBehalf)"); + + if (onBehalf != user) { + assertEq(morpho.collateral(id, user), 0, "collateral(user)"); + assertEq(morpho.supplyShares(id, user), 0, "supplyShares(user)"); + assertEq(morpho.borrowShares(id, user), 0, "borrowShares(user)"); + } } } diff --git a/test/forge/helpers/BaseTest.sol b/test/forge/helpers/BaseTest.sol index dc0f0396..5b82f49c 100644 --- a/test/forge/helpers/BaseTest.sol +++ b/test/forge/helpers/BaseTest.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {Id, MarketParams, Signature, Authorization, IMorpho} from "@morpho-blue/interfaces/IMorpho.sol"; +import "@morpho-blue/interfaces/IMorpho.sol"; import {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol"; import {SharesMathLib} from "@morpho-blue/libraries/SharesMathLib.sol"; @@ -23,7 +23,7 @@ abstract contract BaseTest is Test { using stdJson for string; uint256 internal constant MIN_AMOUNT = 1000; - uint256 internal constant MAX_AMOUNT = 2 ** 64; + uint256 internal constant MAX_AMOUNT = 2 ** 64; // Must be less than or equal to type(uint160).max. uint256 internal constant ORACLE_PRICE_SCALE = 1e36; address internal constant USER = address(0x1234); diff --git a/test/forge/helpers/ForkTest.sol b/test/forge/helpers/ForkTest.sol index dd6b894f..3b79fefe 100644 --- a/test/forge/helpers/ForkTest.sol +++ b/test/forge/helpers/ForkTest.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import "config/Configured.sol"; +import {Permit2Lib} from "@permit2/libraries/Permit2Lib.sol"; +import {PermitHash} from "@permit2/libraries/PermitHash.sol"; import {ChainlinkOracle} from "contracts/oracles/ChainlinkOracle.sol"; +import "config/Configured.sol"; import "./BaseTest.sol"; abstract contract ForkTest is BaseTest, Configured { @@ -16,7 +18,7 @@ abstract contract ForkTest is BaseTest, Configured { uint256 internal snapshotId = type(uint256).max; - MarketParams[] markets; + MarketParams[] allMarketParams; constructor() { _initConfig(); @@ -36,18 +38,20 @@ abstract contract ForkTest is BaseTest, Configured { ChainlinkOracle oracle = new ChainlinkOracle(10 ** (36 + ERC20(configMarket.collateralToken).decimals() - ERC20(configMarket.borrowableToken).decimals()), configMarket.chainlinkFeed); + MarketParams memory marketParams = MarketParams({ + collateralToken: configMarket.collateralToken, + borrowableToken: configMarket.borrowableToken, + oracle: address(oracle), + irm: address(irm), + lltv: configMarket.lltv + }); + vm.startPrank(OWNER); if (!morpho.isLltvEnabled(configMarket.lltv)) morpho.enableLltv(configMarket.lltv); - morpho.createMarket( - MarketParams({ - collateralToken: configMarket.collateralToken, - borrowableToken: configMarket.borrowableToken, - oracle: address(oracle), - irm: address(irm), - lltv: configMarket.lltv - }) - ); + morpho.createMarket(marketParams); vm.stopPrank(); + + allMarketParams.push(marketParams); } } @@ -117,4 +121,8 @@ abstract contract ForkTest is BaseTest, Configured { function _randomLsdNative(uint256 seed) internal view returns (address) { return lsdNatives[seed % lsdNatives.length]; } + + function _randomMarketParams(uint256 seed) internal view returns (MarketParams memory) { + return allMarketParams[seed % allMarketParams.length]; + } } diff --git a/test/forge/helpers/LocalTest.sol b/test/forge/helpers/LocalTest.sol index f492c1e8..dbc19c0a 100644 --- a/test/forge/helpers/LocalTest.sol +++ b/test/forge/helpers/LocalTest.sol @@ -16,8 +16,8 @@ abstract contract LocalTest is BaseTest { uint256 internal constant LLTV = 0.8 ether; - ERC20Mock internal borrowableAsset; - ERC20Mock internal collateralAsset; + ERC20Mock internal borrowableToken; + ERC20Mock internal collateralToken; IOracle internal oracle; MarketParams internal marketParams; @@ -27,14 +27,14 @@ abstract contract LocalTest is BaseTest { super.setUp(); // List a marketParams. - borrowableAsset = new ERC20Mock("borrowable", "B"); - collateralAsset = new ERC20Mock("collateral", "C"); + borrowableToken = new ERC20Mock("borrowable", "B"); + collateralToken = new ERC20Mock("collateral", "C"); oracle = new OracleMock(); irm = new IrmMock(morpho); marketParams = - MarketParams(address(borrowableAsset), address(collateralAsset), address(oracle), address(irm), LLTV); + MarketParams(address(borrowableToken), address(collateralToken), address(oracle), address(irm), LLTV); id = marketParams.id(); OracleMock(address(oracle)).setPrice(ORACLE_PRICE_SCALE); @@ -45,10 +45,10 @@ abstract contract LocalTest is BaseTest { morpho.createMarket(marketParams); vm.stopPrank(); - borrowableAsset.approve(address(morpho), type(uint256).max); - collateralAsset.approve(address(morpho), type(uint256).max); + borrowableToken.approve(address(morpho), type(uint256).max); + collateralToken.approve(address(morpho), type(uint256).max); vm.prank(SUPPLIER); - borrowableAsset.approve(address(morpho), type(uint256).max); + borrowableToken.approve(address(morpho), type(uint256).max); } } From 4590313766dcf5debf0b45888868ce56f095eb46 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 15:12:01 +0200 Subject: [PATCH 14/19] build(lib): bump morpho-blue --- lib/morpho-blue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morpho-blue b/lib/morpho-blue index 590678de..47a06190 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit 590678dec844e290d58bdce83c450f70027aed1d +Subproject commit 47a06190760c8ae321cc6116bcd8f38afbcc0b61 From b410b2c99e0ba7501d78bc56a9944eb695836c7b Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 15:33:12 +0200 Subject: [PATCH 15/19] test(irm): remove constructor arg --- test/forge/helpers/BaseTest.sol | 4 ++-- test/forge/helpers/LocalTest.sol | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/test/forge/helpers/BaseTest.sol b/test/forge/helpers/BaseTest.sol index 5b82f49c..33f66b58 100644 --- a/test/forge/helpers/BaseTest.sol +++ b/test/forge/helpers/BaseTest.sol @@ -36,13 +36,13 @@ abstract contract BaseTest is Test { function setUp() public virtual { morpho = IMorpho(_deploy("lib/morpho-blue/out/Morpho.sol/Morpho.json", abi.encode(OWNER))); - irm = new IrmMock(morpho); + irm = new IrmMock(); vm.prank(OWNER); morpho.enableIrm(address(irm)); - // So tests can borrow/withdraw on behalf of USER without pranking it. vm.prank(USER); + // So tests can borrow/withdraw on behalf of USER without pranking it. morpho.setAuthorization(address(this), true); } diff --git a/test/forge/helpers/LocalTest.sol b/test/forge/helpers/LocalTest.sol index dbc19c0a..c5985737 100644 --- a/test/forge/helpers/LocalTest.sol +++ b/test/forge/helpers/LocalTest.sol @@ -26,19 +26,18 @@ abstract contract LocalTest is BaseTest { function setUp() public virtual override { super.setUp(); - // List a marketParams. borrowableToken = new ERC20Mock("borrowable", "B"); collateralToken = new ERC20Mock("collateral", "C"); - oracle = new OracleMock(); - irm = new IrmMock(morpho); + OracleMock oracleMock = new OracleMock(); + oracle = oracleMock; + + oracleMock.setPrice(ORACLE_PRICE_SCALE); marketParams = MarketParams(address(borrowableToken), address(collateralToken), address(oracle), address(irm), LLTV); id = marketParams.id(); - OracleMock(address(oracle)).setPrice(ORACLE_PRICE_SCALE); - vm.startPrank(OWNER); morpho.enableLltv(LLTV); morpho.enableIrm(address(irm)); From 4225d88390f8bacf81b0f2427dceca565c6fd54c Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 16:38:42 +0200 Subject: [PATCH 16/19] build(morpho-blue): bump morpho-blue --- contracts/bundlers/MorphoBundler.sol | 12 ++++++++---- lib/morpho-blue | 2 +- src/bundler/BundleAction.ts | 15 ++++++++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/contracts/bundlers/MorphoBundler.sol b/contracts/bundlers/MorphoBundler.sol index d90b6699..b7a5cb50 100644 --- a/contracts/bundlers/MorphoBundler.sol +++ b/contracts/bundlers/MorphoBundler.sol @@ -145,12 +145,16 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler { } /// @dev Triggers a liquidation on Blue. - function morphoLiquidate(MarketParams calldata marketparams, address borrower, uint256 seized, bytes memory data) - external - { + function morphoLiquidate( + MarketParams calldata marketparams, + address borrower, + uint256 seizedAssets, + uint256 repaidShares, + bytes memory data + ) external { _approveMaxBlue(marketparams.borrowableToken); - MORPHO.liquidate(marketparams, borrower, seized, data); + MORPHO.liquidate(marketparams, borrower, seizedAssets, repaidShares, data); } /// @dev Triggers a flash loan on Blue. diff --git a/lib/morpho-blue b/lib/morpho-blue index 47a06190..93118d6b 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit 47a06190760c8ae321cc6116bcd8f38afbcc0b61 +Subproject commit 93118d6ba11f727bdea6e050436c03189ab84440 diff --git a/src/bundler/BundleAction.ts b/src/bundler/BundleAction.ts index 278b8026..4703e282 100644 --- a/src/bundler/BundleAction.ts +++ b/src/bundler/BundleAction.ts @@ -46,8 +46,11 @@ class BundleAction { return BundleAction.ERC20_BUNDLER_IFC.encodeFunctionData("transferFrom2", [asset, amount]); } - static morphoSetAuthorization(authorization: AuthorizationStruct, signature: SignatureStruct): BundleCall { - return BundleAction.MORPHO_BUNDLER_IFC.encodeFunctionData("morphoSetAuthorization", [authorization, signature]); + static morphoSetAuthorizationWithSig(authorization: AuthorizationStruct, signature: SignatureStruct): BundleCall { + return BundleAction.MORPHO_BUNDLER_IFC.encodeFunctionData("morphoSetAuthorizationWithSig", [ + authorization, + signature, + ]); } static morphoSupply( @@ -80,7 +83,7 @@ class BundleAction { ]); } - static morphoBorrow(market: MarketStruct, amount: BigNumberish, receiver: string): BundleCall { + static morphoBorrow(market: MarketStruct, amount: BigNumberish, shares: BigNumberish, receiver: string): BundleCall { return BundleAction.MORPHO_BUNDLER_IFC.encodeFunctionData("morphoBorrow", [market, amount, shares, receiver]); } @@ -116,13 +119,15 @@ class BundleAction { static morphoLiquidate( market: MarketStruct, borrower: string, - amount: BigNumberish, + seizedAssets: BigNumberish, + repaidShares: BigNumberish, callbackCalls: BundleCall[], ): BundleCall { return BundleAction.MORPHO_BUNDLER_IFC.encodeFunctionData("morphoLiquidate", [ market, borrower, - amount, + seizedAssets, + repaidShares, BundleAction.MORPHO_BUNDLER_IFC._abiCoder.encode(["bytes[]"], [callbackCalls]), ]); } From f7a96f4b26562050b196e28d70fa30021d53e071 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 16:43:22 +0200 Subject: [PATCH 17/19] test(local): label Morpho --- test/forge/helpers/BaseTest.sol | 1 + test/forge/helpers/LocalTest.sol | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/test/forge/helpers/BaseTest.sol b/test/forge/helpers/BaseTest.sol index 33f66b58..fca1de00 100644 --- a/test/forge/helpers/BaseTest.sol +++ b/test/forge/helpers/BaseTest.sol @@ -35,6 +35,7 @@ abstract contract BaseTest is Test { function setUp() public virtual { morpho = IMorpho(_deploy("lib/morpho-blue/out/Morpho.sol/Morpho.json", abi.encode(OWNER))); + vm.label(address(morpho), "Morpho"); irm = new IrmMock(); diff --git a/test/forge/helpers/LocalTest.sol b/test/forge/helpers/LocalTest.sol index c5985737..9eaa34b9 100644 --- a/test/forge/helpers/LocalTest.sol +++ b/test/forge/helpers/LocalTest.sol @@ -40,7 +40,6 @@ abstract contract LocalTest is BaseTest { vm.startPrank(OWNER); morpho.enableLltv(LLTV); - morpho.enableIrm(address(irm)); morpho.createMarket(marketParams); vm.stopPrank(); From af0bfaddc254b146ba508e0a9b9beffa06a87cae Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 16:49:23 +0200 Subject: [PATCH 18/19] test(mainnet): fix balanceOf --- test/forge/ethereum-mainnet/EthereumBundler.t.sol | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/forge/ethereum-mainnet/EthereumBundler.t.sol b/test/forge/ethereum-mainnet/EthereumBundler.t.sol index 670d22c7..b8bec5a2 100644 --- a/test/forge/ethereum-mainnet/EthereumBundler.t.sol +++ b/test/forge/ethereum-mainnet/EthereumBundler.t.sol @@ -70,6 +70,9 @@ contract EthereumBundlerEthereumTest is ForkTest { data[1] = abi.encodeCall(ERC20Bundler.transferFrom2, (marketParams.borrowableToken, amount)); data[2] = abi.encodeCall(MorphoBundler.morphoSupply, (marketParams, amount, 0, onBehalf, hex"")); + uint256 collateralBalanceBefore = ERC20(marketParams.collateralToken).balanceOf(onBehalf); + uint256 borrowableBalanceBefore = ERC20(marketParams.borrowableToken).balanceOf(onBehalf); + _deal(marketParams.borrowableToken, user, amount); vm.startPrank(user); @@ -82,8 +85,16 @@ contract EthereumBundlerEthereumTest is ForkTest { assertEq(ERC20(marketParams.collateralToken).balanceOf(user), 0, "collateral.balanceOf(user)"); assertEq(ERC20(marketParams.borrowableToken).balanceOf(user), 0, "borrowable.balanceOf(user)"); - assertEq(ERC20(marketParams.collateralToken).balanceOf(onBehalf), 0, "collateral.balanceOf(onBehalf)"); - assertEq(ERC20(marketParams.borrowableToken).balanceOf(onBehalf), 0, "borrowable.balanceOf(onBehalf)"); + assertEq( + ERC20(marketParams.collateralToken).balanceOf(onBehalf), + collateralBalanceBefore, + "collateral.balanceOf(onBehalf)" + ); + assertEq( + ERC20(marketParams.borrowableToken).balanceOf(onBehalf), + borrowableBalanceBefore, + "borrowable.balanceOf(onBehalf)" + ); Id id = marketParams.id(); From b77b297bde2349bdc2b1824bb53fb0a97dff03a9 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 23 Aug 2023 16:57:07 +0200 Subject: [PATCH 19/19] refactor(sig-utils): use constants lib --- test/forge/helpers/SigUtils.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/forge/helpers/SigUtils.sol b/test/forge/helpers/SigUtils.sol index 732145b1..64ba52d3 100644 --- a/test/forge/helpers/SigUtils.sol +++ b/test/forge/helpers/SigUtils.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.0; import {Authorization} from "@morpho-blue/interfaces/IMorpho.sol"; -bytes32 constant AUTHORIZATION_TYPEHASH = - keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)"); +import {AUTHORIZATION_TYPEHASH} from "@morpho-blue/libraries/ConstantsLib.sol"; library SigUtils { /// @dev Computes the hash of the EIP-712 encoded data.