From ad7ab8a0d2b07c980320f7b34aa5d08b6b58714f Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 19 Aug 2024 16:14:03 -0700 Subject: [PATCH 01/50] initial commit on morpho integration --- src/DeFiScripts.sol | 113 +++++++++++++++ src/interfaces/IMetaMorpho.sol | 9 ++ src/interfaces/IMorpho.sol | 34 +++++ .../IMorphoUniversalRewardsDistributor.sol | 8 ++ test/MorphoBlueActions.t.sol | 0 test/MorphoRewardsActions.t.sol | 0 test/MorphoVaultActions.t.sol | 133 ++++++++++++++++++ 7 files changed, 297 insertions(+) create mode 100644 src/interfaces/IMetaMorpho.sol create mode 100644 src/interfaces/IMorpho.sol create mode 100644 src/interfaces/IMorphoUniversalRewardsDistributor.sol create mode 100644 test/MorphoBlueActions.t.sol create mode 100644 test/MorphoRewardsActions.t.sol create mode 100644 test/MorphoVaultActions.t.sol diff --git a/src/DeFiScripts.sol b/src/DeFiScripts.sol index 020177ca..972702fc 100644 --- a/src/DeFiScripts.sol +++ b/src/DeFiScripts.sol @@ -9,6 +9,9 @@ import {QuarkScript} from "quark-core/src/QuarkScript.sol"; import {IComet} from "./interfaces/IComet.sol"; import {ICometRewards} from "./interfaces/ICometRewards.sol"; +import {IMorpho, MarketParams} from "./interfaces/IMorpho.sol"; +import {IMetaMorpho} from "./interfaces/IMetaMorpho.sol"; +import {IMorphoUniversalRewardsDistributor} from "./interfaces/IMorphoUniversalRewardsDistributor.sol"; import {DeFiScriptErrors} from "./lib/DeFiScriptErrors.sol"; contract CometSupplyActions { @@ -327,3 +330,113 @@ contract ApproveAndSwap { IERC20(sellToken).forceApprove(to, 0); } } + +// === Morpho actiosn === +contract MorphoVaultActions { + // To handle non-standard ERC20 tokens (i.e. USDT) + using SafeERC20 for IERC20; + + function deposit(address vault, address asset, uint256 amount) external returns (uint256) { + IERC20(asset).forceApprove(vault, amount); + return IMetaMorpho(vault).deposit(amount, address(this)); + } + + function mint(address vault, address asset, uint256 shares) external returns (uint256 assets) { + IERC20(asset).forceApprove(vault, type(uint256).max); + assets = IMetaMorpho(vault).mint(shares, address(this)); + IERC20(asset).forceApprove(vault, 0); + } + + function withdraw(address vault, uint256 amount) external returns (uint256) { + return IMetaMorpho(vault).withdraw(amount, address(this), address(this)); + } + + function redeem(address vault, uint256 shares) external returns (uint256) { + return IMetaMorpho(vault).redeem(shares, address(this), address(this)); + } +} + +contract MorphoBlueActions { + // To handle non-standard ERC20 tokens (i.e. USDT) + using SafeERC20 for IERC20; + + function borrow( + address morpho, + MarketParams memory marketParams, + uint256 assets, + uint256 shares, + address onBehalf, + address receiver + ) external returns (uint256, uint256) { + return IMorpho(morpho).borrow(marketParams, assets, shares, onBehalf, receiver); + } + + function repay( + address morpho, + MarketParams memory marketParams, + uint256 assets, + uint256 shares, + address onBehalf, + bytes calldata data + ) external returns (uint256 assetsRepaid, uint256 sharesRepaid) { + if (assets > 0) { + IERC20(marketParams.loanToken).forceApprove(morpho, assets); + (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); + } else { + IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); + (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); + IERC20(marketParams.loanToken).forceApprove(morpho, 0); + } + } + + function supplyCollateral( + address morpho, + MarketParams memory marketParams, + uint256 assets, + address onBehalf, + bytes calldata data + ) external { + IERC20(marketParams.collateralToken).forceApprove(morpho, assets); + IMorpho(morpho).supplyCollateral(marketParams, assets, onBehalf, data); + } + + function withdrawCollateral( + address morpho, + MarketParams memory marketParams, + uint256 assets, + address onBehalf, + address receiver + ) external { + IMorpho(morpho).withdrawCollateral(marketParams, assets, onBehalf, receiver); + } +} + +contract MorphoRewardsActions { + function claim(address distributor, address account, address reward, uint256 claimable, bytes32[] calldata proofs) + external + { + IMorphoUniversalRewardsDistributor(distributor).claim(account, reward, claimable, proofs); + } + + function claimAll( + address[] calldata distributors, + address[] calldata accounts, + address[] calldata rewards, + uint256[] calldata claimables, + bytes32[][] calldata proofs + ) external { + if ( + distributors.length != accounts.length || + distributors.length != rewards.length || + distributors.length != claimables.length || + distributors.length != proofs.length + ) { + revert DeFiScriptErrors.InvalidInput(); + } + + for (uint256 i = 0; i < distributors.length; ++i) { + IMorphoUniversalRewardsDistributor(distributors[i]).claim(accounts[i], rewards[i], claimables[i], proofs[i]); + } + } +} +// ====================== diff --git a/src/interfaces/IMetaMorpho.sol b/src/interfaces/IMetaMorpho.sol new file mode 100644 index 00000000..69f8b5ee --- /dev/null +++ b/src/interfaces/IMetaMorpho.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +interface IMetaMorpho { + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + function mint(uint256 shares, address receiver) external returns (uint256 assets); + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol new file mode 100644 index 00000000..74c04b97 --- /dev/null +++ b/src/interfaces/IMorpho.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +interface IMorpho { + function borrow( + MarketParams memory marketParams, + uint256 assets, + uint256 shares, + address onBehalf, + address receiver + ) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed); + + function repay( + MarketParams memory marketParams, + uint256 assets, + uint256 shares, + address onBehalf, + bytes memory data + ) external returns (uint256 assetsRepaid, uint256 sharesRepaid); + + function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data) + external; + + function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) + external; +} + +struct MarketParams { + address loanToken; + address collateralToken; + address oracle; + address irm; + uint256 lltv; +} diff --git a/src/interfaces/IMorphoUniversalRewardsDistributor.sol b/src/interfaces/IMorphoUniversalRewardsDistributor.sol new file mode 100644 index 00000000..fb163d03 --- /dev/null +++ b/src/interfaces/IMorphoUniversalRewardsDistributor.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +interface IMorphoUniversalRewardsDistributor { + function claim(address account, address reward, uint256 claimable, bytes32[] calldata proof) + external + returns (uint256 amount); +} diff --git a/test/MorphoBlueActions.t.sol b/test/MorphoBlueActions.t.sol new file mode 100644 index 00000000..e69de29b diff --git a/test/MorphoRewardsActions.t.sol b/test/MorphoRewardsActions.t.sol new file mode 100644 index 00000000..e69de29b diff --git a/test/MorphoVaultActions.t.sol b/test/MorphoVaultActions.t.sol new file mode 100644 index 00000000..9a563379 --- /dev/null +++ b/test/MorphoVaultActions.t.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "forge-std/StdUtils.sol"; +import "forge-std/StdMath.sol"; + +import {CodeJar} from "codejar/src/CodeJar.sol"; +import {IERC4626} from "openzeppelin/interfaces/IERC4626.sol"; +import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; +import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; + +import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; + +import {YulHelper} from "./lib/YulHelper.sol"; +import {SignatureHelper} from "./lib/SignatureHelper.sol"; +import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; + +import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; + +import "src/DeFiScripts.sol"; + +/** + * Tests for supplying assets to Morpho Vault + */ +contract MorphoVaultActionsTest is Test { + QuarkWalletProxyFactory public factory; + uint256 alicePrivateKey = 0xa11ce; + address alice = vm.addr(alicePrivateKey); + + // Contracts address on mainnet + address constant morphoVault = 0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458; + address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + bytes morphoVaultActionsScripts = new YulHelper().getCode("DeFiScripts.sol/MorphoVaultActions.json"); + + function setUp() public { + // Fork setup + vm.createSelectFork( + string.concat( + "https://node-provider.compound.finance/ethereum-mainnet/", vm.envString("NODE_PROVIDER_BYPASS_KEY") + ), + 20564787 // 2024-08-19 12:34:00 PST + ); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + } + + function testDeposit() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(USDC, address(wallet), 10_000e6); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoVaultActionsScripts, + abi.encodeWithSelector(MorphoVaultActions.deposit.selector, morphoVault, USDC, 10_000e6), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 10_000e6); + assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 0); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); + assertApproxEqAbs(IERC4626(morphoVault).balanceOf(address(wallet)), 9713.4779e18, 0.01e18); + } + + function testMint() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(USDC, address(wallet), 10_000e6); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoVaultActionsScripts, + abi.encodeWithSelector(MorphoVaultActions.mint.selector, morphoVault, USDC, 9000e18), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 10_000e6); + assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 0); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 734.5e6, 1e6); + assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 9000e18); + } + + function testWithdraw() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + // Deal vault shares to wallet, ERC4262 is ERC20 compatible + deal(morphoVault, address(wallet), 10_000e18); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoVaultActionsScripts, + abi.encodeWithSelector(MorphoVaultActions.withdraw.selector, morphoVault, 10_000e6), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 0e6); + assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 10_000e18); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 10_000e6); + assertApproxEqAbs(IERC4626(morphoVault).balanceOf(address(wallet)), 286.5e18, 1e18); + } + + function testRedeem() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + // Deal vault shares to wallet, ERC4262 is ERC20 compatible + deal(morphoVault, address(wallet), 10_000e18); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoVaultActionsScripts, + abi.encodeWithSelector(MorphoVaultActions.redeem.selector, morphoVault, 10_000e18), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 0e6); + assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 10_000e18); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 10_294e6, 1e6); + assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 0e18); + } +} From b7dd24758990aafb725851d0a9fe36a9e4fc1c8b Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Tue, 20 Aug 2024 00:22:58 -0700 Subject: [PATCH 02/50] Morpho DEFI scripts with tests --- src/DeFiScripts.sol | 8 +- src/interfaces/IMorpho.sol | 7 + test/MorphoBlueActions.t.sol | 244 ++++++++++++++++++++++++++++++++ test/MorphoRewardsActions.t.sol | 131 +++++++++++++++++ 4 files changed, 385 insertions(+), 5 deletions(-) diff --git a/src/DeFiScripts.sol b/src/DeFiScripts.sol index 972702fc..a05b4931 100644 --- a/src/DeFiScripts.sol +++ b/src/DeFiScripts.sol @@ -343,7 +343,7 @@ contract MorphoVaultActions { function mint(address vault, address asset, uint256 shares) external returns (uint256 assets) { IERC20(asset).forceApprove(vault, type(uint256).max); - assets = IMetaMorpho(vault).mint(shares, address(this)); + assets = IMetaMorpho(vault).mint(shares, address(this)); IERC20(asset).forceApprove(vault, 0); } @@ -426,10 +426,8 @@ contract MorphoRewardsActions { bytes32[][] calldata proofs ) external { if ( - distributors.length != accounts.length || - distributors.length != rewards.length || - distributors.length != claimables.length || - distributors.length != proofs.length + distributors.length != accounts.length || distributors.length != rewards.length + || distributors.length != claimables.length || distributors.length != proofs.length ) { revert DeFiScriptErrors.InvalidInput(); } diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 74c04b97..e0d1d80c 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -23,6 +23,7 @@ interface IMorpho { function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external; + function position(bytes32 id, address account) external view returns (Position memory); } struct MarketParams { @@ -32,3 +33,9 @@ struct MarketParams { address irm; uint256 lltv; } + +struct Position { + uint256 supplyShares; + uint128 borrowShares; + uint128 collateral; +} diff --git a/test/MorphoBlueActions.t.sol b/test/MorphoBlueActions.t.sol index e69de29b..d95132e3 100644 --- a/test/MorphoBlueActions.t.sol +++ b/test/MorphoBlueActions.t.sol @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "forge-std/StdUtils.sol"; +import "forge-std/StdMath.sol"; + +import {CodeJar} from "codejar/src/CodeJar.sol"; +import {IERC4626} from "openzeppelin/interfaces/IERC4626.sol"; +import {IERC20} from "openzeppelin/interfaces/IERC20.sol"; +import {IMorpho, MarketParams, Position} from "src/interfaces/IMorpho.sol"; +import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; +import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; + +import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; + +import {YulHelper} from "./lib/YulHelper.sol"; +import {SignatureHelper} from "./lib/SignatureHelper.sol"; +import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; + +import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; + +import "src/DeFiScripts.sol"; + +/** + * Tests for Morpho Blue market + */ +contract MorphoBlueActionsTest is Test { + QuarkWalletProxyFactory public factory; + uint256 alicePrivateKey = 0xa11ce; + address alice = vm.addr(alicePrivateKey); + + // Contracts address on mainnet + address constant morphoBlue = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + address constant adaptiveCurveIrm = 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC; + address constant morphoOracle = 0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2; + address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; + MarketParams marketParams = MarketParams(USDC, wstETH, morphoOracle, adaptiveCurveIrm, 0.86e18); + bytes morphoBlueActionsScripts = new YulHelper().getCode("DeFiScripts.sol/MorphoBlueActions.json"); + + function setUp() public { + // Fork setup + vm.createSelectFork( + string.concat( + "https://node-provider.compound.finance/ethereum-mainnet/", vm.envString("NODE_PROVIDER_BYPASS_KEY") + ), + 20564787 // 2024-08-19 12:34:00 PST + ); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + } + + function testBorrowOnAssetsAmount() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + + vm.startPrank(address(wallet)); + IERC20(wstETH).approve(morphoBlue, 10e18); + IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + vm.stopPrank(); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoBlueActionsScripts, + abi.encodeWithSelector( + MorphoBlueActions.borrow.selector, morphoBlue, marketParams, 1000e6, 0, address(wallet), address(wallet) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); + } + + function testBorrowOnSharesAmount() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + + vm.startPrank(address(wallet)); + IERC20(wstETH).approve(morphoBlue, 10e18); + IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + vm.stopPrank(); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoBlueActionsScripts, + abi.encodeWithSelector( + MorphoBlueActions.borrow.selector, morphoBlue, marketParams, 0, 1e15, address(wallet), address(wallet) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 1048.9e6, 1e6); + } + + function testRepayAssetsAmount() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + + vm.startPrank(address(wallet)); + IERC20(wstETH).approve(morphoBlue, 10e18); + IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IMorpho(morphoBlue).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); + vm.stopPrank(); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoBlueActionsScripts, + abi.encodeWithSelector( + MorphoBlueActions.repay.selector, morphoBlue, marketParams, 1000e6, 0, address(wallet), new bytes(0) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); + assertApproxEqAbs( + IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + ); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); + assertApproxEqAbs(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 0, 0.1e14); + } + + function testRepaySharesAmount() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + deal(USDC, address(wallet), 100e6); + + vm.startPrank(address(wallet)); + IERC20(wstETH).approve(morphoBlue, 10e18); + IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IMorpho(morphoBlue).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); + vm.stopPrank(); + + uint256 sharesRepay = IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares; + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoBlueActionsScripts, + abi.encodeWithSelector( + MorphoBlueActions.repay.selector, + morphoBlue, + marketParams, + 0e6, + sharesRepay, + address(wallet), + new bytes(0) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); + assertApproxEqAbs( + IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + ); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.01e6); + assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 0); + } + + function testSupplyCollateral() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoBlueActionsScripts, + abi.encodeWithSelector( + MorphoBlueActions.supplyCollateral.selector, + morphoBlue, + marketParams, + 10e18, + address(wallet), + new bytes(0) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); + assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 0); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); + assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 10e18); + } + + function testWithdrawCollateral() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + + vm.startPrank(address(wallet)); + IERC20(wstETH).approve(morphoBlue, 10e18); + IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + vm.stopPrank(); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoBlueActionsScripts, + abi.encodeWithSelector( + MorphoBlueActions.withdrawCollateral.selector, + morphoBlue, + marketParams, + 10e18, + address(wallet), + address(wallet) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); + assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 10e18); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); + assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 0e18); + } + + // Helper function to convert MarketParams to bytes32 Id + // Reference: https://github.com/morpho-org/morpho-blue/blob/731e3f7ed97cf15f8fe00b86e4be5365eb3802ac/src/libraries/MarketParamsLib.sol + function marketId(MarketParams memory params) public pure returns (bytes32 marketParamsId) { + assembly ("memory-safe") { + marketParamsId := keccak256(params, 160) + } + } +} diff --git a/test/MorphoRewardsActions.t.sol b/test/MorphoRewardsActions.t.sol index e69de29b..34d09c5a 100644 --- a/test/MorphoRewardsActions.t.sol +++ b/test/MorphoRewardsActions.t.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "forge-std/StdUtils.sol"; +import "forge-std/StdMath.sol"; + +import {CodeJar} from "codejar/src/CodeJar.sol"; +import {IERC4626} from "openzeppelin/interfaces/IERC4626.sol"; +import {IERC20} from "openzeppelin/interfaces/IERC20.sol"; +import {IMorpho, MarketParams, Position} from "src/interfaces/IMorpho.sol"; +import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; +import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; + +import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; + +import {YulHelper} from "./lib/YulHelper.sol"; +import {SignatureHelper} from "./lib/SignatureHelper.sol"; +import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; + +import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; + +import "src/DeFiScripts.sol"; + +/** + * Tests for Morpho Rewards Claim + */ +contract MorphoRewardsActionsTest is Test { + QuarkWalletProxyFactory public factory; + uint256 alicePrivateKey = 0xa11ce; + address alice = vm.addr(alicePrivateKey); + + // Contracts address on mainnet + address constant morphoBlue = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + address constant adaptiveCurveIrm = 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC; + address constant morphoOracle = 0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2; + address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; + MarketParams marketParams = MarketParams(USDC, wstETH, morphoOracle, adaptiveCurveIrm, 0.86e18); + bytes morphoRewardsActionsScripts = new YulHelper().getCode("DeFiScripts.sol/MorphoRewardsActions.json"); + bytes morphoBlueActionsScripts = new YulHelper().getCode("DeFiScripts.sol/MorphoBlueActions.json"); + + // Just a list of data from Morpho rewards api for ease of testing on sample account + address sampleAccount = 0x87E0b41CB4d65d788f08c8D82589eA7923D73BA5; + address[] distributors = [0x330eefa8a787552DC5cAd3C3cA644844B1E61Ddb, 0x330eefa8a787552DC5cAd3C3cA644844B1E61Ddb]; + address[] accounts = [sampleAccount, sampleAccount]; + address[] rewards = [0xc55126051B22eBb829D00368f4B12Bde432de5Da, 0xdAC17F958D2ee523a2206206994597C13D831ec7]; + uint256[] claimables = [547387349612, 116]; + bytes32[][] proofs = [ + [ + bytes32(0xce63a4c1fabb68437d0e5edc21b732c5a215f1c5a9ed6a52902f0415e148cc0a), + bytes32(0x23b2ad869c44ff4946d49f0e048edd1303f0cef3679d3e21143c4cfdcde97f20), + bytes32(0x937a82a4d574f809052269e6d4a5613fa4ce333064d012e96e9cc3c04fee7a9c), + bytes32(0xf93fea78509a3b4fe28d963d965ab8819bbf6c08f5789bddde16127e98e6f696), + bytes32(0xbb53cefdee57ab5a04a7be61a15c1ea00beacd0a4adb132dd2e046582eafbec8), + bytes32(0x3dcb507af99e19c829fc2f5a8f57418258230818d4db8dc3080e5cafff5bfd3c), + bytes32(0xca3e0c0cc07c55a02cbc21313bbd9a4d27dae6a28580fbd7dfad74216d4edac3), + bytes32(0x59bdab6ff3d8cd5c682ff241da1d56e9bba6f5c0a739c28629c10ffab8bb9c95), + bytes32(0x56a6fd126541d4a6b4902b78125db2c92b3b9cfb3249bbe3681cc2ccf9a6aa2c), + bytes32(0xfcfad3b73969b50e0369e94db6fcd9301b5e776784620a09c0b52a5cf3326f2b), + bytes32(0x7ee3c650dc15c36a6a0284c40b61391f7ac07f57d50802d92d2ccb7a19ff9dbb) + ], + [ + bytes32(0x7ac5a364f8e3d902a778e6f22d9800304bce9a24108a6b375e9d7afffa586648), + bytes32(0xd0e2f9d70a7c8ddfe74cf2e922067421f06af4c16da32c13d13e6226aff54772), + bytes32(0x8417ffe0c1e153c75ad3bf85f8d52b22ebc5370deda637231cb7fef3238d60b7), + bytes32(0x99baa8011e519a6650c7f8887edde764c9198973be390dfad9a43e8af4603326), + bytes32(0x7db554929334c43f06c93b0917a22765ba0b27684eb3bdbb09eefaad665cf51f), + bytes32(0xd35638edfe77f64712acd397cfddd12da5ba480d05d77b52fa5f9f930b8c4a11), + bytes32(0xee0010ba447e3edda1a034acc142e66ce5c772dc9cbbdf86044e5ee760d4159f), + bytes32(0xedca6a5e9ba49d334eebdc4167e1730fcce5c7e4bbc17638c1cb6b4c42e85e9b), + bytes32(0xfd8786de55c7c2e69c4ede4fe80b5d696875621b7aea7f29736451d3ea667427), + bytes32(0xff695c9c3721e77a593d67cf0cbea7d495d0120ed51e31ab1428a7251665ce37), + bytes32(0x487b38c91a22d77f124819ab4d40eea67b11683459c458933cae385630c90816) + ] + ]; + + function setUp() public { + // Fork setup + vm.createSelectFork( + string.concat( + "https://node-provider.compound.finance/ethereum-mainnet/", vm.envString("NODE_PROVIDER_BYPASS_KEY") + ), + 20568177 // 2024-08-19 23:54:00 PST + ); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + } + + function testClaim() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + // Morpho claim rewards is depends on the account input, so even if the wallet is not the one + // with rewards, wallet can still claim it, but just rewards still goes to the original owner + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoRewardsActionsScripts, + abi.encodeWithSelector( + MorphoRewardsActions.claim.selector, distributors[0], accounts[0], rewards[0], claimables[0], proofs[0] + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(0xc55126051B22eBb829D00368f4B12Bde432de5Da).balanceOf(sampleAccount), 0); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(0xc55126051B22eBb829D00368f4B12Bde432de5Da).balanceOf(sampleAccount), 547387349612); + } + + function testClaimAll() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + // Morpho claim rewards is depends on the account input, so even if the wallet is not the one + // with rewards, wallet can still claim it, but just rewards still goes to the original owner + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoRewardsActionsScripts, + abi.encodeWithSelector( + MorphoRewardsActions.claimAll.selector, distributors, accounts, rewards, claimables, proofs + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(0xc55126051B22eBb829D00368f4B12Bde432de5Da).balanceOf(sampleAccount), 0); + assertEq(IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7).balanceOf(sampleAccount), 0); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(0xc55126051B22eBb829D00368f4B12Bde432de5Da).balanceOf(sampleAccount), 547387349612); + assertEq(IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7).balanceOf(sampleAccount), 116); + } +} From 3c8450922d7352d2e9d67a28574954372dc18acc Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 28 Aug 2024 18:43:33 -0700 Subject: [PATCH 03/50] split morpho out of defi scripts --- src/DeFiScripts.sol | 110 +---------------- src/defi_integrations/MorphoScripts.sol | 157 ++++++++++++++++++++++++ test/MorphoBlueActions.t.sol | 6 +- test/MorphoRewardsActions.t.sol | 5 +- test/MorphoVaultActions.t.sol | 3 +- 5 files changed, 165 insertions(+), 116 deletions(-) create mode 100644 src/defi_integrations/MorphoScripts.sol diff --git a/src/DeFiScripts.sol b/src/DeFiScripts.sol index a05b4931..c4a03f65 100644 --- a/src/DeFiScripts.sol +++ b/src/DeFiScripts.sol @@ -329,112 +329,4 @@ contract ApproveAndSwap { // Approvals to external contracts should always be reset to 0 IERC20(sellToken).forceApprove(to, 0); } -} - -// === Morpho actiosn === -contract MorphoVaultActions { - // To handle non-standard ERC20 tokens (i.e. USDT) - using SafeERC20 for IERC20; - - function deposit(address vault, address asset, uint256 amount) external returns (uint256) { - IERC20(asset).forceApprove(vault, amount); - return IMetaMorpho(vault).deposit(amount, address(this)); - } - - function mint(address vault, address asset, uint256 shares) external returns (uint256 assets) { - IERC20(asset).forceApprove(vault, type(uint256).max); - assets = IMetaMorpho(vault).mint(shares, address(this)); - IERC20(asset).forceApprove(vault, 0); - } - - function withdraw(address vault, uint256 amount) external returns (uint256) { - return IMetaMorpho(vault).withdraw(amount, address(this), address(this)); - } - - function redeem(address vault, uint256 shares) external returns (uint256) { - return IMetaMorpho(vault).redeem(shares, address(this), address(this)); - } -} - -contract MorphoBlueActions { - // To handle non-standard ERC20 tokens (i.e. USDT) - using SafeERC20 for IERC20; - - function borrow( - address morpho, - MarketParams memory marketParams, - uint256 assets, - uint256 shares, - address onBehalf, - address receiver - ) external returns (uint256, uint256) { - return IMorpho(morpho).borrow(marketParams, assets, shares, onBehalf, receiver); - } - - function repay( - address morpho, - MarketParams memory marketParams, - uint256 assets, - uint256 shares, - address onBehalf, - bytes calldata data - ) external returns (uint256 assetsRepaid, uint256 sharesRepaid) { - if (assets > 0) { - IERC20(marketParams.loanToken).forceApprove(morpho, assets); - (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); - } else { - IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); - (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); - IERC20(marketParams.loanToken).forceApprove(morpho, 0); - } - } - - function supplyCollateral( - address morpho, - MarketParams memory marketParams, - uint256 assets, - address onBehalf, - bytes calldata data - ) external { - IERC20(marketParams.collateralToken).forceApprove(morpho, assets); - IMorpho(morpho).supplyCollateral(marketParams, assets, onBehalf, data); - } - - function withdrawCollateral( - address morpho, - MarketParams memory marketParams, - uint256 assets, - address onBehalf, - address receiver - ) external { - IMorpho(morpho).withdrawCollateral(marketParams, assets, onBehalf, receiver); - } -} - -contract MorphoRewardsActions { - function claim(address distributor, address account, address reward, uint256 claimable, bytes32[] calldata proofs) - external - { - IMorphoUniversalRewardsDistributor(distributor).claim(account, reward, claimable, proofs); - } - - function claimAll( - address[] calldata distributors, - address[] calldata accounts, - address[] calldata rewards, - uint256[] calldata claimables, - bytes32[][] calldata proofs - ) external { - if ( - distributors.length != accounts.length || distributors.length != rewards.length - || distributors.length != claimables.length || distributors.length != proofs.length - ) { - revert DeFiScriptErrors.InvalidInput(); - } - - for (uint256 i = 0; i < distributors.length; ++i) { - IMorphoUniversalRewardsDistributor(distributors[i]).claim(accounts[i], rewards[i], claimables[i], proofs[i]); - } - } -} -// ====================== +} \ No newline at end of file diff --git a/src/defi_integrations/MorphoScripts.sol b/src/defi_integrations/MorphoScripts.sol new file mode 100644 index 00000000..2fc6ad6b --- /dev/null +++ b/src/defi_integrations/MorphoScripts.sol @@ -0,0 +1,157 @@ + +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; +import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import {IMorpho, MarketParams} from "src/interfaces/IMorpho.sol"; +import {IMetaMorpho} from "src/interfaces/IMetaMorpho.sol"; +import {IMorphoUniversalRewardsDistributor} from "src/interfaces/IMorphoUniversalRewardsDistributor.sol"; +import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; + +contract MorphoVaultActions { + // To handle non-standard ERC20 tokens (i.e. USDT) + using SafeERC20 for IERC20; + + function deposit(address vault, address asset, uint256 amount) external returns (uint256) { + IERC20(asset).forceApprove(vault, amount); + return IMetaMorpho(vault).deposit(amount, address(this)); + } + + function mint(address vault, address asset, uint256 shares) external returns (uint256 assets) { + IERC20(asset).forceApprove(vault, type(uint256).max); + assets = IMetaMorpho(vault).mint(shares, address(this)); + IERC20(asset).forceApprove(vault, 0); + } + + function withdraw(address vault, uint256 amount) external returns (uint256) { + return IMetaMorpho(vault).withdraw(amount, address(this), address(this)); + } + + function redeem(address vault, uint256 shares) external returns (uint256) { + return IMetaMorpho(vault).redeem(shares, address(this), address(this)); + } +} + +contract MorphoBlueActions { + // To handle non-standard ERC20 tokens (i.e. USDT) + using SafeERC20 for IERC20; + + function borrow( + address morpho, + MarketParams memory marketParams, + uint256 assets, + uint256 shares, + address onBehalf, + address receiver + ) external returns (uint256, uint256) { + return IMorpho(morpho).borrow(marketParams, assets, shares, onBehalf, receiver); + } + + function repay( + address morpho, + MarketParams memory marketParams, + uint256 assets, + uint256 shares, + address onBehalf, + bytes calldata data + ) external returns (uint256 assetsRepaid, uint256 sharesRepaid) { + if (assets > 0) { + IERC20(marketParams.loanToken).forceApprove(morpho, assets); + (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); + } else { + IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); + (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); + IERC20(marketParams.loanToken).forceApprove(morpho, 0); + } + } + + function supplyCollateral( + address morpho, + MarketParams memory marketParams, + uint256 assets, + address onBehalf, + bytes calldata data + ) external { + IERC20(marketParams.collateralToken).forceApprove(morpho, assets); + IMorpho(morpho).supplyCollateral(marketParams, assets, onBehalf, data); + } + + function withdrawCollateral( + address morpho, + MarketParams memory marketParams, + uint256 assets, + address onBehalf, + address receiver + ) external { + IMorpho(morpho).withdrawCollateral(marketParams, assets, onBehalf, receiver); + } + + function repayAndWithdrawCollateral( + address morpho, + MarketParams memory marketParams, + uint256 repayAmount, + uint256 repayShares, + uint256 withdrawAmount, + address onBehalf, + address receiver + ) external { + if (repayAmount > 0 || repayShares > 0) { + if (repayAmount > 0) { + IERC20(marketParams.loanToken).forceApprove(morpho, repayAmount); + IMorpho(morpho).repay(marketParams, repayAmount, 0, onBehalf, new bytes(0)); + } else { + IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); + IMorpho(morpho).repay(marketParams, 0, repayShares, onBehalf, new bytes(0)); + IERC20(marketParams.loanToken).forceApprove(morpho, 0); + } + } + if (withdrawAmount > 0) { + IMorpho(morpho).withdrawCollateral(marketParams, withdrawAmount, onBehalf, receiver); + } + } + + function supplyCollateralAndBorrow( + address morpho, + MarketParams memory marketParams, + uint256 supplyAssetAmount, + uint256 borrowAssetAmount, + address onBehalf, + address receiver + ) external { + if (supplyAssetAmount > 0) { + IERC20(marketParams.collateralToken).forceApprove(morpho, supplyAssetAmount); + IMorpho(morpho).supplyCollateral(marketParams, supplyAssetAmount, onBehalf, new bytes(0)); + } + if (borrowAssetAmount > 0) { + IMorpho(morpho).borrow(marketParams, borrowAssetAmount, 0, onBehalf, receiver); + } + } +} + +contract MorphoRewardsActions { + function claim(address distributor, address account, address reward, uint256 claimable, bytes32[] calldata proofs) + external + { + IMorphoUniversalRewardsDistributor(distributor).claim(account, reward, claimable, proofs); + } + + function claimAll( + address[] calldata distributors, + address[] calldata accounts, + address[] calldata rewards, + uint256[] calldata claimables, + bytes32[][] calldata proofs + ) external { + if ( + distributors.length != accounts.length || distributors.length != rewards.length + || distributors.length != claimables.length || distributors.length != proofs.length + ) { + revert DeFiScriptErrors.InvalidInput(); + } + + for (uint256 i = 0; i < distributors.length; ++i) { + IMorphoUniversalRewardsDistributor(distributors[i]).claim(accounts[i], rewards[i], claimables[i], proofs[i]); + } + } +} \ No newline at end of file diff --git a/test/MorphoBlueActions.t.sol b/test/MorphoBlueActions.t.sol index d95132e3..b5d924f8 100644 --- a/test/MorphoBlueActions.t.sol +++ b/test/MorphoBlueActions.t.sol @@ -19,9 +19,7 @@ import {YulHelper} from "./lib/YulHelper.sol"; import {SignatureHelper} from "./lib/SignatureHelper.sol"; import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; -import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; - -import "src/DeFiScripts.sol"; +import "src/defi_integrations/MorphoScripts.sol"; /** * Tests for Morpho Blue market @@ -38,7 +36,7 @@ contract MorphoBlueActionsTest is Test { address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; MarketParams marketParams = MarketParams(USDC, wstETH, morphoOracle, adaptiveCurveIrm, 0.86e18); - bytes morphoBlueActionsScripts = new YulHelper().getCode("DeFiScripts.sol/MorphoBlueActions.json"); + bytes morphoBlueActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoBlueActions.json"); function setUp() public { // Fork setup diff --git a/test/MorphoRewardsActions.t.sol b/test/MorphoRewardsActions.t.sol index 34d09c5a..98b9abe1 100644 --- a/test/MorphoRewardsActions.t.sol +++ b/test/MorphoRewardsActions.t.sol @@ -22,6 +22,7 @@ import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; import "src/DeFiScripts.sol"; +import "src/defi_integrations/MorphoScripts.sol"; /** * Tests for Morpho Rewards Claim @@ -38,8 +39,8 @@ contract MorphoRewardsActionsTest is Test { address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; MarketParams marketParams = MarketParams(USDC, wstETH, morphoOracle, adaptiveCurveIrm, 0.86e18); - bytes morphoRewardsActionsScripts = new YulHelper().getCode("DeFiScripts.sol/MorphoRewardsActions.json"); - bytes morphoBlueActionsScripts = new YulHelper().getCode("DeFiScripts.sol/MorphoBlueActions.json"); + bytes morphoRewardsActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoRewardsActions.json"); + bytes morphoBlueActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoBlueActions.json"); // Just a list of data from Morpho rewards api for ease of testing on sample account address sampleAccount = 0x87E0b41CB4d65d788f08c8D82589eA7923D73BA5; diff --git a/test/MorphoVaultActions.t.sol b/test/MorphoVaultActions.t.sol index 9a563379..919820e7 100644 --- a/test/MorphoVaultActions.t.sol +++ b/test/MorphoVaultActions.t.sol @@ -20,6 +20,7 @@ import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; import "src/DeFiScripts.sol"; +import "src/defi_integrations/MorphoScripts.sol"; /** * Tests for supplying assets to Morpho Vault @@ -32,7 +33,7 @@ contract MorphoVaultActionsTest is Test { // Contracts address on mainnet address constant morphoVault = 0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458; address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - bytes morphoVaultActionsScripts = new YulHelper().getCode("DeFiScripts.sol/MorphoVaultActions.json"); + bytes morphoVaultActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoVaultActions.json"); function setUp() public { // Fork setup From e441352403e1772dafd0a7911253dbc1840f6b32 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 29 Aug 2024 03:39:18 -0700 Subject: [PATCH 04/50] add tests for bundle actions --- src/DeFiScripts.sol | 2 +- src/defi_integrations/MorphoScripts.sol | 3 +- test/MorphoBlueActions.t.sol | 72 +++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/DeFiScripts.sol b/src/DeFiScripts.sol index c4a03f65..84de6084 100644 --- a/src/DeFiScripts.sol +++ b/src/DeFiScripts.sol @@ -329,4 +329,4 @@ contract ApproveAndSwap { // Approvals to external contracts should always be reset to 0 IERC20(sellToken).forceApprove(to, 0); } -} \ No newline at end of file +} diff --git a/src/defi_integrations/MorphoScripts.sol b/src/defi_integrations/MorphoScripts.sol index 2fc6ad6b..40d86008 100644 --- a/src/defi_integrations/MorphoScripts.sol +++ b/src/defi_integrations/MorphoScripts.sol @@ -1,4 +1,3 @@ - // SPDX-License-Identifier: BSD-3-Clause pragma solidity 0.8.23; @@ -154,4 +153,4 @@ contract MorphoRewardsActions { IMorphoUniversalRewardsDistributor(distributors[i]).claim(accounts[i], rewards[i], claimables[i], proofs[i]); } } -} \ No newline at end of file +} diff --git a/test/MorphoBlueActions.t.sol b/test/MorphoBlueActions.t.sol index b5d924f8..c629670b 100644 --- a/test/MorphoBlueActions.t.sol +++ b/test/MorphoBlueActions.t.sol @@ -232,6 +232,78 @@ contract MorphoBlueActionsTest is Test { assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 0e18); } + function testRepayAndWithdrawCollateral() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + deal(USDC, address(wallet), 100e6); + + vm.startPrank(address(wallet)); + IERC20(wstETH).approve(morphoBlue, 10e18); + IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IMorpho(morphoBlue).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); + vm.stopPrank(); + + uint256 sharesRepay = IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares; + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoBlueActionsScripts, + abi.encodeWithSelector( + MorphoBlueActions.repayAndWithdrawCollateral.selector, + morphoBlue, + marketParams, + 0, + sharesRepay, + 10e18, + address(wallet), + address(wallet) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); + assertApproxEqAbs( + IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + ); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.01e6); + assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 0); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); + } + + function testSupplyCollateralAndBorrow() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + morphoBlueActionsScripts, + abi.encodeWithSelector( + MorphoBlueActions.supplyCollateralAndBorrow.selector, + morphoBlue, + marketParams, + 10e18, + 1000e6, + address(wallet), + address(wallet) + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); + assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 0); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); + assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 10e18); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); + } + // Helper function to convert MarketParams to bytes32 Id // Reference: https://github.com/morpho-org/morpho-blue/blob/731e3f7ed97cf15f8fe00b86e4be5365eb3802ac/src/libraries/MarketParamsLib.sol function marketId(MarketParams memory params) public pure returns (bytes32 marketParamsId) { From 6759c441b49c1e07d75eaae902e12e04744467ef Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 29 Aug 2024 04:20:09 -0700 Subject: [PATCH 05/50] address comments --- src/defi_integrations/MorphoScripts.sol | 109 +++++++++++++++++- src/interfaces/IMetaMorpho.sol | 2 + src/interfaces/IMorpho.sol | 2 + .../IMorphoUniversalRewardsDistributor.sol | 2 + 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/src/defi_integrations/MorphoScripts.sol b/src/defi_integrations/MorphoScripts.sol index 40d86008..b9949eee 100644 --- a/src/defi_integrations/MorphoScripts.sol +++ b/src/defi_integrations/MorphoScripts.sol @@ -12,21 +12,47 @@ contract MorphoVaultActions { // To handle non-standard ERC20 tokens (i.e. USDT) using SafeERC20 for IERC20; + /** + * @notice Deposit assets into a MetaMorpho vault + * @param vault The address of the MetaMorpho vault + * @param asset The address of the asset to deposit + * @param amount The amount of the asset to deposit + * @return shares The amount of shares minted + */ function deposit(address vault, address asset, uint256 amount) external returns (uint256) { IERC20(asset).forceApprove(vault, amount); return IMetaMorpho(vault).deposit(amount, address(this)); } + /** + * @notice Mint shares from a MetaMorpho vault + * @param vault The address of the MetaMorpho vault + * @param asset The address of the asset to mint + * @param shares The amount of shares to mint + * @return assets The amount of assets for shares + */ function mint(address vault, address asset, uint256 shares) external returns (uint256 assets) { IERC20(asset).forceApprove(vault, type(uint256).max); assets = IMetaMorpho(vault).mint(shares, address(this)); IERC20(asset).forceApprove(vault, 0); } + /** + * @notice Withdraw assets from a MetaMorpho vault + * @param vault The address of the MetaMorpho vault + * @param amount The amount of assets to withdraw + * @return shares The amount of shares burned + */ function withdraw(address vault, uint256 amount) external returns (uint256) { return IMetaMorpho(vault).withdraw(amount, address(this), address(this)); } + /** + * @notice Redeem shares from a MetaMorpho vault + * @param vault The address of the MetaMorpho vault + * @param shares The amount of shares to redeem + * @return assets The amount of assets redeemed + */ function redeem(address vault, uint256 shares) external returns (uint256) { return IMetaMorpho(vault).redeem(shares, address(this), address(this)); } @@ -36,6 +62,19 @@ contract MorphoBlueActions { // To handle non-standard ERC20 tokens (i.e. USDT) using SafeERC20 for IERC20; + error InvalidInput(); + + /** + * @notice Borrow assets or shares from a Morpho blue market on behalf of `onBehalf` and send assets to `receiver` + * @param morpho The address of the top level Morpho contract + * @param marketParams The market parameters of the individual morpho blue market to borrow assets from + * @param assets The amount of assets to borrow + * @param shares The amount of shares to borrow + * @param onBehalf The address that will own the increased borrow position + * @param receiver The address that will receive the borrowed assets + * @return assetsBorrowed The amount of assets borrowed + * @return sharesBorrowed The amount of shares minted + */ function borrow( address morpho, MarketParams memory marketParams, @@ -47,6 +86,17 @@ contract MorphoBlueActions { return IMorpho(morpho).borrow(marketParams, assets, shares, onBehalf, receiver); } + /** + * @notice Repay assets or shares in a Morpho blue market on behalf of `onBehalf` + * @param morpho The address of the top level Morpho contract + * @param marketParams The market parameters of the individual morpho blue market + * @param assets The amount of assets to repay + * @param shares The amount of shares to repay + * @param onBehalf The address of the account to repay on behalf of + * @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed + * @return assetsRepaid The amount of assets repaid + * @return sharesRepaid The amount of shares burned + */ function repay( address morpho, MarketParams memory marketParams, @@ -58,13 +108,23 @@ contract MorphoBlueActions { if (assets > 0) { IERC20(marketParams.loanToken).forceApprove(morpho, assets); (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); - } else { + } else if (shares > 0) { IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); IERC20(marketParams.loanToken).forceApprove(morpho, 0); + } else { + revert InvalidInput(); } } + /** + * @notice Supply collateral to a Morpho blue market on behalf of `onBehalf` + * @param morpho The address of the top level Morpho contract + * @param marketParams The market parameters of the individual morpho blue market + * @param assets The amount of assets to supply as collateral + * @param onBehalf The address of the account to supply collateral on behalf of + * @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed + */ function supplyCollateral( address morpho, MarketParams memory marketParams, @@ -76,6 +136,14 @@ contract MorphoBlueActions { IMorpho(morpho).supplyCollateral(marketParams, assets, onBehalf, data); } + /** + * @notice Withdraw collateral from a Morpho blue market on behalf of `onBehalf` to `receiver` + * @param morpho The address of the top level Morpho contract + * @param marketParams The market parameters of the individual morpho blue market + * @param assets The amount of assets to withdraw as collateral + * @param onBehalf The address of the account to withdraw collateral on behalf of + * @param receiver The address of the account to receive the withdrawn collateral + */ function withdrawCollateral( address morpho, MarketParams memory marketParams, @@ -86,6 +154,16 @@ contract MorphoBlueActions { IMorpho(morpho).withdrawCollateral(marketParams, assets, onBehalf, receiver); } + /** + * @notice Repay assets and withdraw collateral from a Morpho blue market on behalf of `onBehalf` and send collateral to `receiver` + * @param morpho The address of the top level Morpho contract + * @param marketParams The market parameters of the individual morpho blue market + * @param repayAmount The amount of assets to repay + * @param repayShares The amount of shares to repay + * @param withdrawAmount The amount of assets to withdraw as collateral + * @param onBehalf The address of the account to repay and withdraw collateral on behalf of + * @param receiver The address of the account to receive the withdrawn collateral + */ function repayAndWithdrawCollateral( address morpho, MarketParams memory marketParams, @@ -99,10 +177,12 @@ contract MorphoBlueActions { if (repayAmount > 0) { IERC20(marketParams.loanToken).forceApprove(morpho, repayAmount); IMorpho(morpho).repay(marketParams, repayAmount, 0, onBehalf, new bytes(0)); - } else { + } else if (repayShares > 0) { IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); IMorpho(morpho).repay(marketParams, 0, repayShares, onBehalf, new bytes(0)); IERC20(marketParams.loanToken).forceApprove(morpho, 0); + } else { + revert InvalidInput(); } } if (withdrawAmount > 0) { @@ -110,6 +190,15 @@ contract MorphoBlueActions { } } + /** + * @notice Supply collateral and borrow assets from a Morpho blue market on behalf of `onBehalf` and send borrowed assets to `receiver` + * @param morpho The address of the top level Morpho contract + * @param marketParams The market parameters of the individual morpho blue market + * @param supplyAssetAmount The amount of assets to supply as collateral + * @param borrowAssetAmount The amount of assets to borrow + * @param onBehalf The address of the account to supply collateral and borrow assets on behalf of + * @param receiver The address of the account to receive the borrowed assets + */ function supplyCollateralAndBorrow( address morpho, MarketParams memory marketParams, @@ -129,12 +218,28 @@ contract MorphoBlueActions { } contract MorphoRewardsActions { + /** + * @notice Claim rewards from a Morpho Universal Rewards Distributor + * @param distributor The address of the Morpho Universal Rewards Distributor + * @param account The address of the account to claim rewards for + * @param reward The address of the reward token to claim + * @param claimable The amount of rewards to claim + * @param proofs The proofs to claim the rewards (reference: https://docs.morpho.org/rewards/tutorials/claim-rewards/) + */ function claim(address distributor, address account, address reward, uint256 claimable, bytes32[] calldata proofs) external { IMorphoUniversalRewardsDistributor(distributor).claim(account, reward, claimable, proofs); } + /** + * @notice Claim rewards from multiple Morpho Universal Rewards Distributors in one transaction + * @param distributors The addresses of the Morpho Universal Rewards Distributors + * @param accounts The addresses of the accounts to claim rewards for + * @param rewards The addresses of the reward tokens to claim + * @param claimables The amounts of rewards to claim + * @param proofs The batch of proofs to claim the rewards (reference: https://docs.morpho.org/rewards/tutorials/claim-rewards/) + */ function claimAll( address[] calldata distributors, address[] calldata accounts, diff --git a/src/interfaces/IMetaMorpho.sol b/src/interfaces/IMetaMorpho.sol index 69f8b5ee..b4720ae2 100644 --- a/src/interfaces/IMetaMorpho.sol +++ b/src/interfaces/IMetaMorpho.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity 0.8.23; +/// @dev Interface for MetaMorpho (vault) for earn +/// Reference: https://github.com/morpho-org/metamorpho/blob/main/src/MetaMorpho.sol interface IMetaMorpho { function deposit(uint256 assets, address receiver) external returns (uint256 shares); function mint(uint256 shares, address receiver) external returns (uint256 assets); diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index e0d1d80c..7345199b 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity 0.8.23; +/// @dev Interface for Morpho blue markets +/// Reference: https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol interface IMorpho { function borrow( MarketParams memory marketParams, diff --git a/src/interfaces/IMorphoUniversalRewardsDistributor.sol b/src/interfaces/IMorphoUniversalRewardsDistributor.sol index fb163d03..8d9f8300 100644 --- a/src/interfaces/IMorphoUniversalRewardsDistributor.sol +++ b/src/interfaces/IMorphoUniversalRewardsDistributor.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity 0.8.23; +/// @dev Interface for Morpho Universal Rewards Distributor +/// Reference: https://github.com/morpho-org/universal-rewards-distributor/blob/main/src/UniversalRewardsDistributor.sol interface IMorphoUniversalRewardsDistributor { function claim(address account, address reward, uint256 claimable, bytes32[] calldata proof) external From 95dffa95cd058af8e9c7e07572f8dd4f25277f9b Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Sat, 31 Aug 2024 00:44:59 -0700 Subject: [PATCH 06/50] remove unnecessary if check --- src/defi_integrations/MorphoScripts.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/defi_integrations/MorphoScripts.sol b/src/defi_integrations/MorphoScripts.sol index b9949eee..2072ffce 100644 --- a/src/defi_integrations/MorphoScripts.sol +++ b/src/defi_integrations/MorphoScripts.sol @@ -177,12 +177,10 @@ contract MorphoBlueActions { if (repayAmount > 0) { IERC20(marketParams.loanToken).forceApprove(morpho, repayAmount); IMorpho(morpho).repay(marketParams, repayAmount, 0, onBehalf, new bytes(0)); - } else if (repayShares > 0) { + } else { IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); IMorpho(morpho).repay(marketParams, 0, repayShares, onBehalf, new bytes(0)); IERC20(marketParams.loanToken).forceApprove(morpho, 0); - } else { - revert InvalidInput(); } } if (withdrawAmount > 0) { From 104b7bdd413c93149f98b387cb2b54d96a3c264e Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Sat, 31 Aug 2024 00:46:07 -0700 Subject: [PATCH 07/50] remove unnecessary if check --- src/defi_integrations/MorphoScripts.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/defi_integrations/MorphoScripts.sol b/src/defi_integrations/MorphoScripts.sol index 2072ffce..5b21bab1 100644 --- a/src/defi_integrations/MorphoScripts.sol +++ b/src/defi_integrations/MorphoScripts.sol @@ -62,8 +62,6 @@ contract MorphoBlueActions { // To handle non-standard ERC20 tokens (i.e. USDT) using SafeERC20 for IERC20; - error InvalidInput(); - /** * @notice Borrow assets or shares from a Morpho blue market on behalf of `onBehalf` and send assets to `receiver` * @param morpho The address of the top level Morpho contract @@ -108,12 +106,10 @@ contract MorphoBlueActions { if (assets > 0) { IERC20(marketParams.loanToken).forceApprove(morpho, assets); (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); - } else if (shares > 0) { + } else { IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); IERC20(marketParams.loanToken).forceApprove(morpho, 0); - } else { - revert InvalidInput(); } } From c28d9573efee43cd562138a64c44226a022b470f Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Fri, 30 Aug 2024 17:26:31 -0700 Subject: [PATCH 08/50] remove unused import --- src/DeFiScripts.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/DeFiScripts.sol b/src/DeFiScripts.sol index 84de6084..020177ca 100644 --- a/src/DeFiScripts.sol +++ b/src/DeFiScripts.sol @@ -9,9 +9,6 @@ import {QuarkScript} from "quark-core/src/QuarkScript.sol"; import {IComet} from "./interfaces/IComet.sol"; import {ICometRewards} from "./interfaces/ICometRewards.sol"; -import {IMorpho, MarketParams} from "./interfaces/IMorpho.sol"; -import {IMetaMorpho} from "./interfaces/IMetaMorpho.sol"; -import {IMorphoUniversalRewardsDistributor} from "./interfaces/IMorphoUniversalRewardsDistributor.sol"; import {DeFiScriptErrors} from "./lib/DeFiScriptErrors.sol"; contract CometSupplyActions { From 9d7f4868913417d952a8e1d34e263900b42f2954 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Sat, 31 Aug 2024 03:35:10 -0700 Subject: [PATCH 09/50] address comments, move MorphoScripts out of defi_integrations folder, and update referneces --- src/{defi_integrations => }/MorphoScripts.sol | 0 test/MorphoBlueActions.t.sol | 2 +- test/MorphoRewardsActions.t.sol | 2 +- test/MorphoVaultActions.t.sol | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/{defi_integrations => }/MorphoScripts.sol (100%) diff --git a/src/defi_integrations/MorphoScripts.sol b/src/MorphoScripts.sol similarity index 100% rename from src/defi_integrations/MorphoScripts.sol rename to src/MorphoScripts.sol diff --git a/test/MorphoBlueActions.t.sol b/test/MorphoBlueActions.t.sol index c629670b..143a2857 100644 --- a/test/MorphoBlueActions.t.sol +++ b/test/MorphoBlueActions.t.sol @@ -19,7 +19,7 @@ import {YulHelper} from "./lib/YulHelper.sol"; import {SignatureHelper} from "./lib/SignatureHelper.sol"; import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; -import "src/defi_integrations/MorphoScripts.sol"; +import "src/MorphoScripts.sol"; /** * Tests for Morpho Blue market diff --git a/test/MorphoRewardsActions.t.sol b/test/MorphoRewardsActions.t.sol index 98b9abe1..b0b2ca9d 100644 --- a/test/MorphoRewardsActions.t.sol +++ b/test/MorphoRewardsActions.t.sol @@ -22,7 +22,7 @@ import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; import "src/DeFiScripts.sol"; -import "src/defi_integrations/MorphoScripts.sol"; +import "src/MorphoScripts.sol"; /** * Tests for Morpho Rewards Claim diff --git a/test/MorphoVaultActions.t.sol b/test/MorphoVaultActions.t.sol index 919820e7..be768271 100644 --- a/test/MorphoVaultActions.t.sol +++ b/test/MorphoVaultActions.t.sol @@ -20,7 +20,7 @@ import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; import "src/DeFiScripts.sol"; -import "src/defi_integrations/MorphoScripts.sol"; +import "src/MorphoScripts.sol"; /** * Tests for supplying assets to Morpho Vault From 687abbc4007c8294e498cd8eeeac664fbbf09b33 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Sun, 1 Sep 2024 05:46:18 -0700 Subject: [PATCH 10/50] add repay max custom logics to MorphoScripts so client doesn't have to pass in complicated shares conversion amount, and also dropped risky changable receiver+owner address, so that client app can't forge iput of those fields to steal users' fund --- src/MorphoScripts.sol | 175 ++++++++++++------ src/interfaces/IMetaMorpho.sol | 4 +- ...oBlueActions.t.sol => MorphoActions.t.sol} | 171 ++++++++--------- test/MorphoRewardsActions.t.sol | 2 +- 4 files changed, 204 insertions(+), 148 deletions(-) rename test/{MorphoBlueActions.t.sol => MorphoActions.t.sol} (61%) diff --git a/src/MorphoScripts.sol b/src/MorphoScripts.sol index 5b21bab1..84941fda 100644 --- a/src/MorphoScripts.sol +++ b/src/MorphoScripts.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.23; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; -import {IMorpho, MarketParams} from "src/interfaces/IMorpho.sol"; +import {IMorpho, MarketParams, Position} from "src/interfaces/IMorpho.sol"; import {IMetaMorpho} from "src/interfaces/IMetaMorpho.sol"; import {IMorphoUniversalRewardsDistributor} from "src/interfaces/IMorphoUniversalRewardsDistributor.sol"; import {DeFiScriptErrors} from "src/lib/DeFiScriptErrors.sol"; @@ -21,7 +21,7 @@ contract MorphoVaultActions { */ function deposit(address vault, address asset, uint256 amount) external returns (uint256) { IERC20(asset).forceApprove(vault, amount); - return IMetaMorpho(vault).deposit(amount, address(this)); + return IMetaMorpho(vault).deposit({assets: amount, receiver: address(this)}); } /** @@ -33,7 +33,7 @@ contract MorphoVaultActions { */ function mint(address vault, address asset, uint256 shares) external returns (uint256 assets) { IERC20(asset).forceApprove(vault, type(uint256).max); - assets = IMetaMorpho(vault).mint(shares, address(this)); + assets = IMetaMorpho(vault).mint({shares: shares, receiver: address(this)}); IERC20(asset).forceApprove(vault, 0); } @@ -44,7 +44,7 @@ contract MorphoVaultActions { * @return shares The amount of shares burned */ function withdraw(address vault, uint256 amount) external returns (uint256) { - return IMetaMorpho(vault).withdraw(amount, address(this), address(this)); + return IMetaMorpho(vault).withdraw({assets: amount, receiver: address(this), owner: address(this)}); } /** @@ -54,11 +54,26 @@ contract MorphoVaultActions { * @return assets The amount of assets redeemed */ function redeem(address vault, uint256 shares) external returns (uint256) { - return IMetaMorpho(vault).redeem(shares, address(this), address(this)); + return IMetaMorpho(vault).redeem({shares: shares, receiver: address(this), owner: address(this)}); + } + + /** + * @notice Redeem all shares from a MetaMorpho vault + * As suggested from MetaMorpho.sol doc, it is recommended to not use their + * redeemMax function to retrieve max shares to redeem due to cost. + * Instead will just use balanceOf(vault) to optimistically redeem all shares. + * @param vault The address of the MetaMorpho vault + */ + function redeemAll(address vault) external returns (uint256) { + return IMetaMorpho(vault).redeem({ + shares: IMetaMorpho(vault).balanceOf(address(this)), + receiver: address(this), + owner: address(this) + }); } } -contract MorphoBlueActions { +contract MorphoActions { // To handle non-standard ERC20 tokens (i.e. USDT) using SafeERC20 for IERC20; @@ -68,20 +83,20 @@ contract MorphoBlueActions { * @param marketParams The market parameters of the individual morpho blue market to borrow assets from * @param assets The amount of assets to borrow * @param shares The amount of shares to borrow - * @param onBehalf The address that will own the increased borrow position - * @param receiver The address that will receive the borrowed assets * @return assetsBorrowed The amount of assets borrowed * @return sharesBorrowed The amount of shares minted */ - function borrow( - address morpho, - MarketParams memory marketParams, - uint256 assets, - uint256 shares, - address onBehalf, - address receiver - ) external returns (uint256, uint256) { - return IMorpho(morpho).borrow(marketParams, assets, shares, onBehalf, receiver); + function borrow(address morpho, MarketParams memory marketParams, uint256 assets, uint256 shares) + external + returns (uint256, uint256) + { + return IMorpho(morpho).borrow({ + marketParams: marketParams, + assets: assets, + shares: shares, + onBehalf: address(this), + receiver: address(this) + }); } /** @@ -90,7 +105,6 @@ contract MorphoBlueActions { * @param marketParams The market parameters of the individual morpho blue market * @param assets The amount of assets to repay * @param shares The amount of shares to repay - * @param onBehalf The address of the account to repay on behalf of * @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed * @return assetsRepaid The amount of assets repaid * @return sharesRepaid The amount of shares burned @@ -100,15 +114,26 @@ contract MorphoBlueActions { MarketParams memory marketParams, uint256 assets, uint256 shares, - address onBehalf, bytes calldata data ) external returns (uint256 assetsRepaid, uint256 sharesRepaid) { if (assets > 0) { IERC20(marketParams.loanToken).forceApprove(morpho, assets); - (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); + (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay({ + marketParams: marketParams, + assets: assets, + shares: shares, + onBehalf: address(this), + data: data + }); } else { IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); - (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay(marketParams, assets, shares, onBehalf, data); + (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay({ + marketParams: marketParams, + assets: assets, + shares: shares, + onBehalf: address(this), + data: data + }); IERC20(marketParams.loanToken).forceApprove(morpho, 0); } } @@ -118,18 +143,18 @@ contract MorphoBlueActions { * @param morpho The address of the top level Morpho contract * @param marketParams The market parameters of the individual morpho blue market * @param assets The amount of assets to supply as collateral - * @param onBehalf The address of the account to supply collateral on behalf of * @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed */ - function supplyCollateral( - address morpho, - MarketParams memory marketParams, - uint256 assets, - address onBehalf, - bytes calldata data - ) external { + function supplyCollateral(address morpho, MarketParams memory marketParams, uint256 assets, bytes calldata data) + external + { IERC20(marketParams.collateralToken).forceApprove(morpho, assets); - IMorpho(morpho).supplyCollateral(marketParams, assets, onBehalf, data); + IMorpho(morpho).supplyCollateral({ + marketParams: marketParams, + assets: assets, + onBehalf: address(this), + data: data + }); } /** @@ -137,50 +162,71 @@ contract MorphoBlueActions { * @param morpho The address of the top level Morpho contract * @param marketParams The market parameters of the individual morpho blue market * @param assets The amount of assets to withdraw as collateral - * @param onBehalf The address of the account to withdraw collateral on behalf of - * @param receiver The address of the account to receive the withdrawn collateral */ - function withdrawCollateral( - address morpho, - MarketParams memory marketParams, - uint256 assets, - address onBehalf, - address receiver - ) external { - IMorpho(morpho).withdrawCollateral(marketParams, assets, onBehalf, receiver); + function withdrawCollateral(address morpho, MarketParams memory marketParams, uint256 assets) external { + IMorpho(morpho).withdrawCollateral({ + marketParams: marketParams, + assets: assets, + onBehalf: address(this), + receiver: address(this) + }); } /** * @notice Repay assets and withdraw collateral from a Morpho blue market on behalf of `onBehalf` and send collateral to `receiver` * @param morpho The address of the top level Morpho contract * @param marketParams The market parameters of the individual morpho blue market - * @param repayAmount The amount of assets to repay + * @param repayAmount The amount of assets to repay, pass in `type(uint256).max` to repay max * @param repayShares The amount of shares to repay * @param withdrawAmount The amount of assets to withdraw as collateral - * @param onBehalf The address of the account to repay and withdraw collateral on behalf of - * @param receiver The address of the account to receive the withdrawn collateral */ function repayAndWithdrawCollateral( address morpho, MarketParams memory marketParams, uint256 repayAmount, uint256 repayShares, - uint256 withdrawAmount, - address onBehalf, - address receiver + uint256 withdrawAmount ) external { if (repayAmount > 0 || repayShares > 0) { - if (repayAmount > 0) { + if (repayAmount == type(uint256).max) { + // Repay max + IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); + IMorpho(morpho).repay({ + marketParams: marketParams, + assets: 0, + shares: IMorpho(morpho).position(marketId(marketParams), address(this)).borrowShares, + onBehalf: address(this), + data: new bytes(0) + }); + IERC20(marketParams.loanToken).forceApprove(morpho, 0); + } else if (repayAmount > 0) { IERC20(marketParams.loanToken).forceApprove(morpho, repayAmount); - IMorpho(morpho).repay(marketParams, repayAmount, 0, onBehalf, new bytes(0)); + IMorpho(morpho).repay({ + marketParams: marketParams, + assets: repayAmount, + shares: 0, + onBehalf: address(this), + data: new bytes(0) + }); } else { IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); - IMorpho(morpho).repay(marketParams, 0, repayShares, onBehalf, new bytes(0)); + IMorpho(morpho).repay({ + marketParams: marketParams, + assets: 0, + shares: repayShares, + onBehalf: address(this), + data: new bytes(0) + }); IERC20(marketParams.loanToken).forceApprove(morpho, 0); } } if (withdrawAmount > 0) { - IMorpho(morpho).withdrawCollateral(marketParams, withdrawAmount, onBehalf, receiver); + IMorpho(morpho).withdrawCollateral({ + marketParams: marketParams, + assets: withdrawAmount, + onBehalf: address(this), + receiver: address(this) + }); } } @@ -190,23 +236,38 @@ contract MorphoBlueActions { * @param marketParams The market parameters of the individual morpho blue market * @param supplyAssetAmount The amount of assets to supply as collateral * @param borrowAssetAmount The amount of assets to borrow - * @param onBehalf The address of the account to supply collateral and borrow assets on behalf of - * @param receiver The address of the account to receive the borrowed assets */ function supplyCollateralAndBorrow( address morpho, MarketParams memory marketParams, uint256 supplyAssetAmount, - uint256 borrowAssetAmount, - address onBehalf, - address receiver + uint256 borrowAssetAmount ) external { if (supplyAssetAmount > 0) { IERC20(marketParams.collateralToken).forceApprove(morpho, supplyAssetAmount); - IMorpho(morpho).supplyCollateral(marketParams, supplyAssetAmount, onBehalf, new bytes(0)); + IMorpho(morpho).supplyCollateral({ + marketParams: marketParams, + assets: supplyAssetAmount, + onBehalf: address(this), + data: new bytes(0) + }); } if (borrowAssetAmount > 0) { - IMorpho(morpho).borrow(marketParams, borrowAssetAmount, 0, onBehalf, receiver); + IMorpho(morpho).borrow({ + marketParams: marketParams, + assets: borrowAssetAmount, + shares: 0, + onBehalf: address(this), + receiver: address(this) + }); + } + } + + // Helper function to convert MarketParams to bytes32 Id + // Reference: https://github.com/morpho-org/morpho-blue/blob/731e3f7ed97cf15f8fe00b86e4be5365eb3802ac/src/libraries/MarketParamsLib.sol + function marketId(MarketParams memory params) public pure returns (bytes32 marketParamsId) { + assembly ("memory-safe") { + marketParamsId := keccak256(params, 160) } } } diff --git a/src/interfaces/IMetaMorpho.sol b/src/interfaces/IMetaMorpho.sol index b4720ae2..6ed71770 100644 --- a/src/interfaces/IMetaMorpho.sol +++ b/src/interfaces/IMetaMorpho.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity 0.8.23; +import {IERC4626} from "openzeppelin/interfaces/IERC4626.sol"; + /// @dev Interface for MetaMorpho (vault) for earn /// Reference: https://github.com/morpho-org/metamorpho/blob/main/src/MetaMorpho.sol -interface IMetaMorpho { +interface IMetaMorpho is IERC4626 { function deposit(uint256 assets, address receiver) external returns (uint256 shares); function mint(uint256 shares, address receiver) external returns (uint256 assets); function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); diff --git a/test/MorphoBlueActions.t.sol b/test/MorphoActions.t.sol similarity index 61% rename from test/MorphoBlueActions.t.sol rename to test/MorphoActions.t.sol index 143a2857..99c033ea 100644 --- a/test/MorphoBlueActions.t.sol +++ b/test/MorphoActions.t.sol @@ -24,19 +24,19 @@ import "src/MorphoScripts.sol"; /** * Tests for Morpho Blue market */ -contract MorphoBlueActionsTest is Test { +contract MorphoActionsTest is Test { QuarkWalletProxyFactory public factory; uint256 alicePrivateKey = 0xa11ce; address alice = vm.addr(alicePrivateKey); // Contracts address on mainnet - address constant morphoBlue = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + address constant morpho = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; address constant adaptiveCurveIrm = 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC; address constant morphoOracle = 0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2; address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; MarketParams marketParams = MarketParams(USDC, wstETH, morphoOracle, adaptiveCurveIrm, 0.86e18); - bytes morphoBlueActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoBlueActions.json"); + bytes MorphoActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoActions.json"); function setUp() public { // Fork setup @@ -56,16 +56,14 @@ contract MorphoBlueActionsTest is Test { deal(wstETH, address(wallet), 10e18); vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morphoBlue, 10e18); - IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IERC20(wstETH).approve(morpho, 10e18); + IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); vm.stopPrank(); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, - morphoBlueActionsScripts, - abi.encodeWithSelector( - MorphoBlueActions.borrow.selector, morphoBlue, marketParams, 1000e6, 0, address(wallet), address(wallet) - ), + MorphoActionsScripts, + abi.encodeWithSelector(MorphoActions.borrow.selector, morpho, marketParams, 1000e6, 0), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); @@ -82,16 +80,14 @@ contract MorphoBlueActionsTest is Test { deal(wstETH, address(wallet), 10e18); vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morphoBlue, 10e18); - IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IERC20(wstETH).approve(morpho, 10e18); + IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); vm.stopPrank(); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, - morphoBlueActionsScripts, - abi.encodeWithSelector( - MorphoBlueActions.borrow.selector, morphoBlue, marketParams, 0, 1e15, address(wallet), address(wallet) - ), + MorphoActionsScripts, + abi.encodeWithSelector(MorphoActions.borrow.selector, morpho, marketParams, 0, 1e15), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); @@ -108,28 +104,26 @@ contract MorphoBlueActionsTest is Test { deal(wstETH, address(wallet), 10e18); vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morphoBlue, 10e18); - IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); - IMorpho(morphoBlue).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); + IERC20(wstETH).approve(morpho, 10e18); + IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IMorpho(morpho).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); vm.stopPrank(); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, - morphoBlueActionsScripts, - abi.encodeWithSelector( - MorphoBlueActions.repay.selector, morphoBlue, marketParams, 1000e6, 0, address(wallet), new bytes(0) - ), + MorphoActionsScripts, + abi.encodeWithSelector(MorphoActions.repay.selector, morpho, marketParams, 1000e6, 0, new bytes(0)), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); assertApproxEqAbs( - IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 ); vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); - assertApproxEqAbs(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 0, 0.1e14); + assertApproxEqAbs(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0, 0.1e14); } function testRepaySharesAmount() public { @@ -140,35 +134,27 @@ contract MorphoBlueActionsTest is Test { deal(USDC, address(wallet), 100e6); vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morphoBlue, 10e18); - IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); - IMorpho(morphoBlue).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); + IERC20(wstETH).approve(morpho, 10e18); + IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IMorpho(morpho).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); vm.stopPrank(); - uint256 sharesRepay = IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares; + uint256 sharesRepay = IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares; QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, - morphoBlueActionsScripts, - abi.encodeWithSelector( - MorphoBlueActions.repay.selector, - morphoBlue, - marketParams, - 0e6, - sharesRepay, - address(wallet), - new bytes(0) - ), + MorphoActionsScripts, + abi.encodeWithSelector(MorphoActions.repay.selector, morpho, marketParams, 0e6, sharesRepay, new bytes(0)), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); assertApproxEqAbs( - IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 ); vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.01e6); - assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 0); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0); } function testSupplyCollateral() public { @@ -179,24 +165,17 @@ contract MorphoBlueActionsTest is Test { QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, - morphoBlueActionsScripts, - abi.encodeWithSelector( - MorphoBlueActions.supplyCollateral.selector, - morphoBlue, - marketParams, - 10e18, - address(wallet), - new bytes(0) - ), + MorphoActionsScripts, + abi.encodeWithSelector(MorphoActions.supplyCollateral.selector, morpho, marketParams, 10e18, new bytes(0)), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); - assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 0); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 0); vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 10e18); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 10e18); } function testWithdrawCollateral() public { @@ -206,30 +185,23 @@ contract MorphoBlueActionsTest is Test { deal(wstETH, address(wallet), 10e18); vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morphoBlue, 10e18); - IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IERC20(wstETH).approve(morpho, 10e18); + IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); vm.stopPrank(); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, - morphoBlueActionsScripts, - abi.encodeWithSelector( - MorphoBlueActions.withdrawCollateral.selector, - morphoBlue, - marketParams, - 10e18, - address(wallet), - address(wallet) - ), + MorphoActionsScripts, + abi.encodeWithSelector(MorphoActions.withdrawCollateral.selector, morpho, marketParams, 10e18), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 10e18); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 10e18); vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); - assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 0e18); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 0e18); } function testRepayAndWithdrawCollateral() public { @@ -240,24 +212,17 @@ contract MorphoBlueActionsTest is Test { deal(USDC, address(wallet), 100e6); vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morphoBlue, 10e18); - IMorpho(morphoBlue).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); - IMorpho(morphoBlue).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); + IERC20(wstETH).approve(morpho, 10e18); + IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IMorpho(morpho).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); vm.stopPrank(); - uint256 sharesRepay = IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares; + uint256 sharesRepay = IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares; QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, - morphoBlueActionsScripts, + MorphoActionsScripts, abi.encodeWithSelector( - MorphoBlueActions.repayAndWithdrawCollateral.selector, - morphoBlue, - marketParams, - 0, - sharesRepay, - 10e18, - address(wallet), - address(wallet) + MorphoActions.repayAndWithdrawCollateral.selector, morpho, marketParams, 0, sharesRepay, 10e18 ), ScriptType.ScriptSource ); @@ -265,12 +230,12 @@ contract MorphoBlueActionsTest is Test { assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); assertApproxEqAbs( - IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 ); vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.01e6); - assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).borrowShares, 0); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); } @@ -282,28 +247,56 @@ contract MorphoBlueActionsTest is Test { QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, - morphoBlueActionsScripts, + MorphoActionsScripts, abi.encodeWithSelector( - MorphoBlueActions.supplyCollateralAndBorrow.selector, - morphoBlue, - marketParams, - 10e18, - 1000e6, - address(wallet), - address(wallet) + MorphoActions.supplyCollateralAndBorrow.selector, morpho, marketParams, 10e18, 1000e6 ), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); - assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 0); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 0); vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - assertEq(IMorpho(morphoBlue).position(marketId(marketParams), address(wallet)).collateral, 10e18); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 10e18); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); } + function testRepayMaxAndWithdrawCollateral() public { + vm.pauseGasMetering(); + QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); + + deal(wstETH, address(wallet), 10e18); + deal(USDC, address(wallet), 100e6); + + vm.startPrank(address(wallet)); + IERC20(wstETH).approve(morpho, 10e18); + IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); + IMorpho(morpho).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); + vm.stopPrank(); + + QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + wallet, + MorphoActionsScripts, + abi.encodeWithSelector( + MorphoActions.repayAndWithdrawCollateral.selector, morpho, marketParams, type(uint256).max, 0, 10e18 + ), + ScriptType.ScriptSource + ); + (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); + assertApproxEqAbs( + IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + ); + vm.resumeGasMetering(); + wallet.executeQuarkOperation(op, v, r, s); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.01e6); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); + } + // Helper function to convert MarketParams to bytes32 Id // Reference: https://github.com/morpho-org/morpho-blue/blob/731e3f7ed97cf15f8fe00b86e4be5365eb3802ac/src/libraries/MarketParamsLib.sol function marketId(MarketParams memory params) public pure returns (bytes32 marketParamsId) { diff --git a/test/MorphoRewardsActions.t.sol b/test/MorphoRewardsActions.t.sol index b0b2ca9d..46bd6463 100644 --- a/test/MorphoRewardsActions.t.sol +++ b/test/MorphoRewardsActions.t.sol @@ -40,7 +40,7 @@ contract MorphoRewardsActionsTest is Test { address constant wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; MarketParams marketParams = MarketParams(USDC, wstETH, morphoOracle, adaptiveCurveIrm, 0.86e18); bytes morphoRewardsActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoRewardsActions.json"); - bytes morphoBlueActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoBlueActions.json"); + bytes MorphoActionsScripts = new YulHelper().getCode("MorphoScripts.sol/MorphoActions.json"); // Just a list of data from Morpho rewards api for ease of testing on sample account address sampleAccount = 0x87E0b41CB4d65d788f08c8D82589eA7923D73BA5; From 1d88bc82bca7484a5088463e1d1c858be5e571bc Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Sun, 1 Sep 2024 16:36:49 -0700 Subject: [PATCH 11/50] After some consideration, we can just trim off all other functions as builder will only use repayAndwithdraw or supplyandBorrow, builder will never call inidividual supply/withdraw/repay...etc on its own, so trim those functions off to save cost. Also extend the ability to support repayMax and withdrawMax into function for both Morpho and MetaMorpho vault actions --- src/MorphoScripts.sol | 153 ++++------------------------------ test/MorphoActions.t.sol | 147 +++----------------------------- test/MorphoVaultActions.t.sol | 43 ---------- 3 files changed, 28 insertions(+), 315 deletions(-) diff --git a/src/MorphoScripts.sol b/src/MorphoScripts.sol index 84941fda..60a7e9b5 100644 --- a/src/MorphoScripts.sol +++ b/src/MorphoScripts.sol @@ -17,59 +17,31 @@ contract MorphoVaultActions { * @param vault The address of the MetaMorpho vault * @param asset The address of the asset to deposit * @param amount The amount of the asset to deposit - * @return shares The amount of shares minted */ - function deposit(address vault, address asset, uint256 amount) external returns (uint256) { + function deposit(address vault, address asset, uint256 amount) external { IERC20(asset).forceApprove(vault, amount); - return IMetaMorpho(vault).deposit({assets: amount, receiver: address(this)}); - } - - /** - * @notice Mint shares from a MetaMorpho vault - * @param vault The address of the MetaMorpho vault - * @param asset The address of the asset to mint - * @param shares The amount of shares to mint - * @return assets The amount of assets for shares - */ - function mint(address vault, address asset, uint256 shares) external returns (uint256 assets) { - IERC20(asset).forceApprove(vault, type(uint256).max); - assets = IMetaMorpho(vault).mint({shares: shares, receiver: address(this)}); - IERC20(asset).forceApprove(vault, 0); + IMetaMorpho(vault).deposit({assets: amount, receiver: address(this)}); } /** * @notice Withdraw assets from a MetaMorpho vault - * @param vault The address of the MetaMorpho vault - * @param amount The amount of assets to withdraw - * @return shares The amount of shares burned - */ - function withdraw(address vault, uint256 amount) external returns (uint256) { - return IMetaMorpho(vault).withdraw({assets: amount, receiver: address(this), owner: address(this)}); - } - - /** - * @notice Redeem shares from a MetaMorpho vault - * @param vault The address of the MetaMorpho vault - * @param shares The amount of shares to redeem - * @return assets The amount of assets redeemed - */ - function redeem(address vault, uint256 shares) external returns (uint256) { - return IMetaMorpho(vault).redeem({shares: shares, receiver: address(this), owner: address(this)}); - } - - /** - * @notice Redeem all shares from a MetaMorpho vault * As suggested from MetaMorpho.sol doc, it is recommended to not use their * redeemMax function to retrieve max shares to redeem due to cost. - * Instead will just use balanceOf(vault) to optimistically redeem all shares. + * Instead will just use balanceOf(vault) to optimistically redeem all shares when amount is `type(uint256).max`. * @param vault The address of the MetaMorpho vault + * @param amount The amount of assets to withdraw, if it is `type(uint256).max`, it will withdraw max */ - function redeemAll(address vault) external returns (uint256) { - return IMetaMorpho(vault).redeem({ - shares: IMetaMorpho(vault).balanceOf(address(this)), - receiver: address(this), - owner: address(this) - }); + function withdraw(address vault, uint256 amount) external { + if (amount == type(uint256).max) { + // Withdraw max + IMetaMorpho(vault).redeem({ + shares: IMetaMorpho(vault).balanceOf(address(this)), + receiver: address(this), + owner: address(this) + }); + } else { + IMetaMorpho(vault).withdraw({assets: amount, receiver: address(this), owner: address(this)}); + } } } @@ -77,101 +49,6 @@ contract MorphoActions { // To handle non-standard ERC20 tokens (i.e. USDT) using SafeERC20 for IERC20; - /** - * @notice Borrow assets or shares from a Morpho blue market on behalf of `onBehalf` and send assets to `receiver` - * @param morpho The address of the top level Morpho contract - * @param marketParams The market parameters of the individual morpho blue market to borrow assets from - * @param assets The amount of assets to borrow - * @param shares The amount of shares to borrow - * @return assetsBorrowed The amount of assets borrowed - * @return sharesBorrowed The amount of shares minted - */ - function borrow(address morpho, MarketParams memory marketParams, uint256 assets, uint256 shares) - external - returns (uint256, uint256) - { - return IMorpho(morpho).borrow({ - marketParams: marketParams, - assets: assets, - shares: shares, - onBehalf: address(this), - receiver: address(this) - }); - } - - /** - * @notice Repay assets or shares in a Morpho blue market on behalf of `onBehalf` - * @param morpho The address of the top level Morpho contract - * @param marketParams The market parameters of the individual morpho blue market - * @param assets The amount of assets to repay - * @param shares The amount of shares to repay - * @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed - * @return assetsRepaid The amount of assets repaid - * @return sharesRepaid The amount of shares burned - */ - function repay( - address morpho, - MarketParams memory marketParams, - uint256 assets, - uint256 shares, - bytes calldata data - ) external returns (uint256 assetsRepaid, uint256 sharesRepaid) { - if (assets > 0) { - IERC20(marketParams.loanToken).forceApprove(morpho, assets); - (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay({ - marketParams: marketParams, - assets: assets, - shares: shares, - onBehalf: address(this), - data: data - }); - } else { - IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); - (assetsRepaid, sharesRepaid) = IMorpho(morpho).repay({ - marketParams: marketParams, - assets: assets, - shares: shares, - onBehalf: address(this), - data: data - }); - IERC20(marketParams.loanToken).forceApprove(morpho, 0); - } - } - - /** - * @notice Supply collateral to a Morpho blue market on behalf of `onBehalf` - * @param morpho The address of the top level Morpho contract - * @param marketParams The market parameters of the individual morpho blue market - * @param assets The amount of assets to supply as collateral - * @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed - */ - function supplyCollateral(address morpho, MarketParams memory marketParams, uint256 assets, bytes calldata data) - external - { - IERC20(marketParams.collateralToken).forceApprove(morpho, assets); - IMorpho(morpho).supplyCollateral({ - marketParams: marketParams, - assets: assets, - onBehalf: address(this), - data: data - }); - } - - /** - * @notice Withdraw collateral from a Morpho blue market on behalf of `onBehalf` to `receiver` - * @param morpho The address of the top level Morpho contract - * @param marketParams The market parameters of the individual morpho blue market - * @param assets The amount of assets to withdraw as collateral - */ - function withdrawCollateral(address morpho, MarketParams memory marketParams, uint256 assets) external { - IMorpho(morpho).withdrawCollateral({ - marketParams: marketParams, - assets: assets, - onBehalf: address(this), - receiver: address(this) - }); - } - /** * @notice Repay assets and withdraw collateral from a Morpho blue market on behalf of `onBehalf` and send collateral to `receiver` * @param morpho The address of the top level Morpho contract diff --git a/test/MorphoActions.t.sol b/test/MorphoActions.t.sol index 99c033ea..7b099669 100644 --- a/test/MorphoActions.t.sol +++ b/test/MorphoActions.t.sol @@ -49,168 +49,44 @@ contract MorphoActionsTest is Test { factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); } - function testBorrowOnAssetsAmount() public { - vm.pauseGasMetering(); - QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - - deal(wstETH, address(wallet), 10e18); - - vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morpho, 10e18); - IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); - vm.stopPrank(); - - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - wallet, - MorphoActionsScripts, - abi.encodeWithSelector(MorphoActions.borrow.selector, morpho, marketParams, 1000e6, 0), - ScriptType.ScriptSource - ); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); - assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); - vm.resumeGasMetering(); - wallet.executeQuarkOperation(op, v, r, s); - assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); - } - - function testBorrowOnSharesAmount() public { - vm.pauseGasMetering(); - QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - - deal(wstETH, address(wallet), 10e18); - - vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morpho, 10e18); - IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); - vm.stopPrank(); - - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - wallet, - MorphoActionsScripts, - abi.encodeWithSelector(MorphoActions.borrow.selector, morpho, marketParams, 0, 1e15), - ScriptType.ScriptSource - ); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); - assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); - vm.resumeGasMetering(); - wallet.executeQuarkOperation(op, v, r, s); - assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 1048.9e6, 1e6); - } - - function testRepayAssetsAmount() public { + function testRepayAndWithdrawCollateral() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); deal(wstETH, address(wallet), 10e18); - vm.startPrank(address(wallet)); IERC20(wstETH).approve(morpho, 10e18); IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); IMorpho(morpho).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); vm.stopPrank(); + // Repay 800 USDC and withdraw 5 wstETH collateral QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, MorphoActionsScripts, - abi.encodeWithSelector(MorphoActions.repay.selector, morpho, marketParams, 1000e6, 0, new bytes(0)), + abi.encodeWithSelector( + MorphoActions.repayAndWithdrawCollateral.selector, morpho, marketParams, 800e6, 0, 5e18 + ), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); assertApproxEqAbs( IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 ); vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); - assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); - assertApproxEqAbs(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0, 0.1e14); - } - - function testRepaySharesAmount() public { - vm.pauseGasMetering(); - QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - - deal(wstETH, address(wallet), 10e18); - deal(USDC, address(wallet), 100e6); - - vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morpho, 10e18); - IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); - IMorpho(morpho).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); - vm.stopPrank(); - - uint256 sharesRepay = IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares; - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - wallet, - MorphoActionsScripts, - abi.encodeWithSelector(MorphoActions.repay.selector, morpho, marketParams, 0e6, sharesRepay, new bytes(0)), - ScriptType.ScriptSource - ); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); - assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); - assertApproxEqAbs( - IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 - ); - vm.resumeGasMetering(); - wallet.executeQuarkOperation(op, v, r, s); - assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.01e6); - assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0); - } - - function testSupplyCollateral() public { - vm.pauseGasMetering(); - QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - - deal(wstETH, address(wallet), 10e18); - - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - wallet, - MorphoActionsScripts, - abi.encodeWithSelector(MorphoActions.supplyCollateral.selector, morpho, marketParams, 10e18, new bytes(0)), - ScriptType.ScriptSource - ); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); - assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); - assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 0); - vm.resumeGasMetering(); - wallet.executeQuarkOperation(op, v, r, s); - assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 10e18); - } - - function testWithdrawCollateral() public { - vm.pauseGasMetering(); - QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - - deal(wstETH, address(wallet), 10e18); - - vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morpho, 10e18); - IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); - vm.stopPrank(); - - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - wallet, - MorphoActionsScripts, - abi.encodeWithSelector(MorphoActions.withdrawCollateral.selector, morpho, marketParams, 10e18), - ScriptType.ScriptSource - ); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); - assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 10e18); - vm.resumeGasMetering(); - wallet.executeQuarkOperation(op, v, r, s); - assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); - assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 0e18); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 200e6); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 5e18); } - function testRepayAndWithdrawCollateral() public { + function testRepayAndWithdrawColalteralInShares() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); deal(wstETH, address(wallet), 10e18); deal(USDC, address(wallet), 100e6); - vm.startPrank(address(wallet)); IERC20(wstETH).approve(morpho, 10e18); IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); @@ -218,6 +94,7 @@ contract MorphoActionsTest is Test { vm.stopPrank(); uint256 sharesRepay = IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares; + // Repay all borrowed shares and withdraw all 10 wstETH collateral QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, MorphoActionsScripts, @@ -234,7 +111,7 @@ contract MorphoActionsTest is Test { ); vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); - assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.01e6); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.05e6); assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); } @@ -245,6 +122,7 @@ contract MorphoActionsTest is Test { deal(wstETH, address(wallet), 10e18); + // Supply 10 wstETH as collateral and borrow 1000 USDC QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, MorphoActionsScripts, @@ -276,6 +154,7 @@ contract MorphoActionsTest is Test { IMorpho(morpho).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); vm.stopPrank(); + // Repay max USDC and withdraw all 10 wstETH collateral QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, MorphoActionsScripts, diff --git a/test/MorphoVaultActions.t.sol b/test/MorphoVaultActions.t.sol index be768271..e4e35e54 100644 --- a/test/MorphoVaultActions.t.sol +++ b/test/MorphoVaultActions.t.sol @@ -67,27 +67,6 @@ contract MorphoVaultActionsTest is Test { assertApproxEqAbs(IERC4626(morphoVault).balanceOf(address(wallet)), 9713.4779e18, 0.01e18); } - function testMint() public { - vm.pauseGasMetering(); - QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - - deal(USDC, address(wallet), 10_000e6); - - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - wallet, - morphoVaultActionsScripts, - abi.encodeWithSelector(MorphoVaultActions.mint.selector, morphoVault, USDC, 9000e18), - ScriptType.ScriptSource - ); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); - assertEq(IERC20(USDC).balanceOf(address(wallet)), 10_000e6); - assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 0); - vm.resumeGasMetering(); - wallet.executeQuarkOperation(op, v, r, s); - assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 734.5e6, 1e6); - assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 9000e18); - } - function testWithdraw() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); @@ -109,26 +88,4 @@ contract MorphoVaultActionsTest is Test { assertEq(IERC20(USDC).balanceOf(address(wallet)), 10_000e6); assertApproxEqAbs(IERC4626(morphoVault).balanceOf(address(wallet)), 286.5e18, 1e18); } - - function testRedeem() public { - vm.pauseGasMetering(); - QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - - // Deal vault shares to wallet, ERC4262 is ERC20 compatible - deal(morphoVault, address(wallet), 10_000e18); - - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - wallet, - morphoVaultActionsScripts, - abi.encodeWithSelector(MorphoVaultActions.redeem.selector, morphoVault, 10_000e18), - ScriptType.ScriptSource - ); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); - assertEq(IERC20(USDC).balanceOf(address(wallet)), 0e6); - assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 10_000e18); - vm.resumeGasMetering(); - wallet.executeQuarkOperation(op, v, r, s); - assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 10_294e6, 1e6); - assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 0e18); - } } From 965d1721984afff694868b5ac6d8fdd17e5e6dd4 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Tue, 3 Sep 2024 16:50:35 -0700 Subject: [PATCH 12/50] address comments --- test/MorphoActions.t.sol | 8 ++++++++ test/MorphoRewardsActions.t.sol | 4 ++++ test/MorphoVaultActions.t.sol | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/test/MorphoActions.t.sol b/test/MorphoActions.t.sol index 7b099669..a40ec328 100644 --- a/test/MorphoActions.t.sol +++ b/test/MorphoActions.t.sol @@ -75,8 +75,10 @@ contract MorphoActionsTest is Test { assertApproxEqAbs( IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 ); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 200e6); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 5e18); } @@ -109,8 +111,10 @@ contract MorphoActionsTest is Test { assertApproxEqAbs( IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 ); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.05e6); assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); @@ -134,8 +138,10 @@ contract MorphoActionsTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 0); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 10e18); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); @@ -169,8 +175,10 @@ contract MorphoActionsTest is Test { assertApproxEqAbs( IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 ); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.01e6); assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); diff --git a/test/MorphoRewardsActions.t.sol b/test/MorphoRewardsActions.t.sol index 46bd6463..c4c76ec2 100644 --- a/test/MorphoRewardsActions.t.sol +++ b/test/MorphoRewardsActions.t.sol @@ -103,8 +103,10 @@ contract MorphoRewardsActionsTest is Test { ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(0xc55126051B22eBb829D00368f4B12Bde432de5Da).balanceOf(sampleAccount), 0); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(0xc55126051B22eBb829D00368f4B12Bde432de5Da).balanceOf(sampleAccount), 547387349612); } @@ -124,8 +126,10 @@ contract MorphoRewardsActionsTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(0xc55126051B22eBb829D00368f4B12Bde432de5Da).balanceOf(sampleAccount), 0); assertEq(IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7).balanceOf(sampleAccount), 0); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(0xc55126051B22eBb829D00368f4B12Bde432de5Da).balanceOf(sampleAccount), 547387349612); assertEq(IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7).balanceOf(sampleAccount), 116); } diff --git a/test/MorphoVaultActions.t.sol b/test/MorphoVaultActions.t.sol index e4e35e54..928be15f 100644 --- a/test/MorphoVaultActions.t.sol +++ b/test/MorphoVaultActions.t.sol @@ -61,10 +61,15 @@ contract MorphoVaultActionsTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(USDC).balanceOf(address(wallet)), 10_000e6); assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 0); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); assertApproxEqAbs(IERC4626(morphoVault).balanceOf(address(wallet)), 9713.4779e18, 0.01e18); + assertApproxEqAbs( + IERC4626(morphoVault).convertToAssets(IERC4626(morphoVault).balanceOf(address(wallet))), 10_000e6, 0.01e6 + ); } function testWithdraw() public { @@ -83,8 +88,10 @@ contract MorphoVaultActionsTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(USDC).balanceOf(address(wallet)), 0e6); assertEq(IERC4626(morphoVault).balanceOf(address(wallet)), 10_000e18); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertEq(IERC20(USDC).balanceOf(address(wallet)), 10_000e6); assertApproxEqAbs(IERC4626(morphoVault).balanceOf(address(wallet)), 286.5e18, 1e18); } From 75cdb39bde146da08fa1faf65e39c02836f34215 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 13:23:31 -0700 Subject: [PATCH 13/50] incorporate SharesMathLib for replacing confusing shares# assertions in tests --- src/interfaces/IMorpho.sol | 11 +++++ src/vendor/manifest.json | 20 +++++++++ src/vendor/morpho_blue_periphery/MathLib.sol | 45 +++++++++++++++++++ .../morpho_blue_periphery/SharesMathLib.sol | 45 +++++++++++++++++++ test/MorphoActions.t.sol | 20 ++++++--- test/MorphoVaultActions.t.sol | 2 - 6 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 src/vendor/morpho_blue_periphery/MathLib.sol create mode 100644 src/vendor/morpho_blue_periphery/SharesMathLib.sol diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 7345199b..06b2a197 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -26,6 +26,17 @@ interface IMorpho { function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external; function position(bytes32 id, address account) external view returns (Position memory); + function market(bytes32 id) + external + view + returns ( + uint128 totalSupplyAssets, + uint128 totalSupplyShares, + uint128 totalBorrowAssets, + uint128 totalBorrowShares, + uint128 lastUpdate, + uint128 fee + ); } struct MarketParams { diff --git a/src/vendor/manifest.json b/src/vendor/manifest.json index 40cc65e2..f4aac3ca 100644 --- a/src/vendor/manifest.json +++ b/src/vendor/manifest.json @@ -131,6 +131,26 @@ ] } ] + }, + "morpho_blue_periphery/MathLib.sol":{ + "source": { + "git": { + "repo": "git@github.com:morpho-org/morpho-blue.git", + "commit": "0448402af51b8293ed36653de43cbee8d4d2bfda", + "path": "src/libraries/MathLib.sol" + } + }, + "patches": [] + }, + "morpho_blue_periphery/SharesMathLib.sol":{ + "source": { + "git": { + "repo": "git@github.com:morpho-org/morpho-blue.git", + "commit": "0448402af51b8293ed36653de43cbee8d4d2bfda", + "path": "src/libraries/SharesMathLib.sol" + } + }, + "patches": [] } } } \ No newline at end of file diff --git a/src/vendor/morpho_blue_periphery/MathLib.sol b/src/vendor/morpho_blue_periphery/MathLib.sol new file mode 100644 index 00000000..653db4f8 --- /dev/null +++ b/src/vendor/morpho_blue_periphery/MathLib.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +uint256 constant WAD = 1e18; + +/// @title MathLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Library to manage fixed-point arithmetic. +library MathLib { + /// @dev Returns (`x` * `y`) / `WAD` rounded down. + function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, y, WAD); + } + + /// @dev Returns (`x` * `WAD`) / `y` rounded down. + function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, WAD, y); + } + + /// @dev Returns (`x` * `WAD`) / `y` rounded up. + function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, WAD, y); + } + + /// @dev Returns (`x` * `y`) / `d` rounded down. + function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { + return (x * y) / d; + } + + /// @dev Returns (`x` * `y`) / `d` rounded up. + function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { + return (x * y + (d - 1)) / d; + } + + /// @dev Returns the sum of the first three non-zero terms of a Taylor expansion of e^(nx) - 1, to approximate a + /// continuous compound interest rate. + function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) { + uint256 firstTerm = x * n; + uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD); + uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD); + + return firstTerm + secondTerm + thirdTerm; + } +} diff --git a/src/vendor/morpho_blue_periphery/SharesMathLib.sol b/src/vendor/morpho_blue_periphery/SharesMathLib.sol new file mode 100644 index 00000000..3ed7115b --- /dev/null +++ b/src/vendor/morpho_blue_periphery/SharesMathLib.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {MathLib} from "./MathLib.sol"; + +/// @title SharesMathLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Shares management library. +/// @dev This implementation mitigates share price manipulations, using OpenZeppelin's method of virtual shares: +/// https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack. +library SharesMathLib { + using MathLib for uint256; + + /// @dev The number of virtual shares has been chosen low enough to prevent overflows, and high enough to ensure + /// high precision computations. + /// @dev Virtual shares can never be redeemed for the assets they are entitled to, but it is assumed the share price + /// stays low enough not to inflate these assets to a significant value. + /// @dev Warning: The assets to which virtual borrow shares are entitled behave like unrealizable bad debt. + uint256 internal constant VIRTUAL_SHARES = 1e6; + + /// @dev A number of virtual assets of 1 enforces a conversion rate between shares and assets when a market is + /// empty. + uint256 internal constant VIRTUAL_ASSETS = 1; + + /// @dev Calculates the value of `assets` quoted in shares, rounding down. + function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { + return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS); + } + + /// @dev Calculates the value of `shares` quoted in assets, rounding down. + function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { + return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); + } + + /// @dev Calculates the value of `assets` quoted in shares, rounding up. + function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { + return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS); + } + + /// @dev Calculates the value of `shares` quoted in assets, rounding up. + function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { + return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); + } +} diff --git a/test/MorphoActions.t.sol b/test/MorphoActions.t.sol index a40ec328..9f64a0ec 100644 --- a/test/MorphoActions.t.sol +++ b/test/MorphoActions.t.sol @@ -18,7 +18,7 @@ import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.s import {YulHelper} from "./lib/YulHelper.sol"; import {SignatureHelper} from "./lib/SignatureHelper.sol"; import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; - +import {SharesMathLib} from "src/vendor/morpho_blue_periphery/SharesMathLib.sol"; import "src/MorphoScripts.sol"; /** @@ -72,8 +72,10 @@ contract MorphoActionsTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - assertApproxEqAbs( - IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + (,, uint128 totalBorrowAssets, uint128 totalBorrowShares,,) = IMorpho(morpho).market(marketId(marketParams)); + assertEq( + IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, + SharesMathLib.toSharesUp(1000e6, totalBorrowAssets, totalBorrowShares) ); vm.resumeGasMetering(); @@ -108,8 +110,10 @@ contract MorphoActionsTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - assertApproxEqAbs( - IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + (,, uint128 totalBorrowAssets, uint128 totalBorrowShares,,) = IMorpho(morpho).market(marketId(marketParams)); + assertEq( + IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, + SharesMathLib.toSharesUp(1000e6, totalBorrowAssets, totalBorrowShares) ); vm.resumeGasMetering(); @@ -172,8 +176,10 @@ contract MorphoActionsTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - assertApproxEqAbs( - IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 9.533e14, 0.1e14 + (,, uint128 totalBorrowAssets, uint128 totalBorrowShares,,) = IMorpho(morpho).market(marketId(marketParams)); + assertEq( + IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, + SharesMathLib.toSharesUp(1000e6, totalBorrowAssets, totalBorrowShares) ); vm.resumeGasMetering(); diff --git a/test/MorphoVaultActions.t.sol b/test/MorphoVaultActions.t.sol index 928be15f..6d001ff4 100644 --- a/test/MorphoVaultActions.t.sol +++ b/test/MorphoVaultActions.t.sol @@ -66,7 +66,6 @@ contract MorphoVaultActionsTest is Test { wallet.executeQuarkOperation(op, v, r, s); assertEq(IERC20(USDC).balanceOf(address(wallet)), 0); - assertApproxEqAbs(IERC4626(morphoVault).balanceOf(address(wallet)), 9713.4779e18, 0.01e18); assertApproxEqAbs( IERC4626(morphoVault).convertToAssets(IERC4626(morphoVault).balanceOf(address(wallet))), 10_000e6, 0.01e6 ); @@ -93,6 +92,5 @@ contract MorphoVaultActionsTest is Test { wallet.executeQuarkOperation(op, v, r, s); assertEq(IERC20(USDC).balanceOf(address(wallet)), 10_000e6); - assertApproxEqAbs(IERC4626(morphoVault).balanceOf(address(wallet)), 286.5e18, 1e18); } } From dc4d380cd63942231aadaef3d210564cf0ff1a5b Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 14:28:14 -0700 Subject: [PATCH 14/50] abstract shares away from morpho scripts, remove shares repay test --- src/MorphoScripts.sol | 17 +++------------ test/MorphoActions.t.sol | 45 ++-------------------------------------- 2 files changed, 5 insertions(+), 57 deletions(-) diff --git a/src/MorphoScripts.sol b/src/MorphoScripts.sol index 60a7e9b5..017a4eda 100644 --- a/src/MorphoScripts.sol +++ b/src/MorphoScripts.sol @@ -54,17 +54,15 @@ contract MorphoActions { * @param morpho The address of the top level Morpho contract * @param marketParams The market parameters of the individual morpho blue market * @param repayAmount The amount of assets to repay, pass in `type(uint256).max` to repay max - * @param repayShares The amount of shares to repay * @param withdrawAmount The amount of assets to withdraw as collateral */ function repayAndWithdrawCollateral( address morpho, MarketParams memory marketParams, uint256 repayAmount, - uint256 repayShares, uint256 withdrawAmount ) external { - if (repayAmount > 0 || repayShares > 0) { + if (repayAmount > 0) { if (repayAmount == type(uint256).max) { // Repay max IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); @@ -76,7 +74,7 @@ contract MorphoActions { data: new bytes(0) }); IERC20(marketParams.loanToken).forceApprove(morpho, 0); - } else if (repayAmount > 0) { + } else { IERC20(marketParams.loanToken).forceApprove(morpho, repayAmount); IMorpho(morpho).repay({ marketParams: marketParams, @@ -85,18 +83,9 @@ contract MorphoActions { onBehalf: address(this), data: new bytes(0) }); - } else { - IERC20(marketParams.loanToken).forceApprove(morpho, type(uint256).max); - IMorpho(morpho).repay({ - marketParams: marketParams, - assets: 0, - shares: repayShares, - onBehalf: address(this), - data: new bytes(0) - }); - IERC20(marketParams.loanToken).forceApprove(morpho, 0); } } + if (withdrawAmount > 0) { IMorpho(morpho).withdrawCollateral({ marketParams: marketParams, diff --git a/test/MorphoActions.t.sol b/test/MorphoActions.t.sol index 9f64a0ec..0c8ef5da 100644 --- a/test/MorphoActions.t.sol +++ b/test/MorphoActions.t.sol @@ -64,9 +64,7 @@ contract MorphoActionsTest is Test { QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, MorphoActionsScripts, - abi.encodeWithSelector( - MorphoActions.repayAndWithdrawCollateral.selector, morpho, marketParams, 800e6, 0, 5e18 - ), + abi.encodeWithSelector(MorphoActions.repayAndWithdrawCollateral.selector, morpho, marketParams, 800e6, 5e18), ScriptType.ScriptSource ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); @@ -85,45 +83,6 @@ contract MorphoActionsTest is Test { assertEq(IERC20(wstETH).balanceOf(address(wallet)), 5e18); } - function testRepayAndWithdrawColalteralInShares() public { - vm.pauseGasMetering(); - QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - - deal(wstETH, address(wallet), 10e18); - deal(USDC, address(wallet), 100e6); - vm.startPrank(address(wallet)); - IERC20(wstETH).approve(morpho, 10e18); - IMorpho(morpho).supplyCollateral(marketParams, 10e18, address(wallet), new bytes(0)); - IMorpho(morpho).borrow(marketParams, 1000e6, 0, address(wallet), address(wallet)); - vm.stopPrank(); - - uint256 sharesRepay = IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares; - // Repay all borrowed shares and withdraw all 10 wstETH collateral - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - wallet, - MorphoActionsScripts, - abi.encodeWithSelector( - MorphoActions.repayAndWithdrawCollateral.selector, morpho, marketParams, 0, sharesRepay, 10e18 - ), - ScriptType.ScriptSource - ); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); - assertEq(IERC20(USDC).balanceOf(address(wallet)), 1100e6); - assertEq(IERC20(wstETH).balanceOf(address(wallet)), 0); - (,, uint128 totalBorrowAssets, uint128 totalBorrowShares,,) = IMorpho(morpho).market(marketId(marketParams)); - assertEq( - IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, - SharesMathLib.toSharesUp(1000e6, totalBorrowAssets, totalBorrowShares) - ); - - vm.resumeGasMetering(); - wallet.executeQuarkOperation(op, v, r, s); - - assertApproxEqAbs(IERC20(USDC).balanceOf(address(wallet)), 100e6, 0.05e6); - assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, 0); - assertEq(IERC20(wstETH).balanceOf(address(wallet)), 10e18); - } - function testSupplyCollateralAndBorrow() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); @@ -169,7 +128,7 @@ contract MorphoActionsTest is Test { wallet, MorphoActionsScripts, abi.encodeWithSelector( - MorphoActions.repayAndWithdrawCollateral.selector, morpho, marketParams, type(uint256).max, 0, 10e18 + MorphoActions.repayAndWithdrawCollateral.selector, morpho, marketParams, type(uint256).max, 10e18 ), ScriptType.ScriptSource ); From 105010551c7c7e6f18cac2b6b5df384a5cdd9006 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 14:56:11 -0700 Subject: [PATCH 15/50] add more assertions to test --- test/MorphoActions.t.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/MorphoActions.t.sol b/test/MorphoActions.t.sol index 0c8ef5da..6600a436 100644 --- a/test/MorphoActions.t.sol +++ b/test/MorphoActions.t.sol @@ -81,6 +81,12 @@ contract MorphoActionsTest is Test { assertEq(IERC20(USDC).balanceOf(address(wallet)), 200e6); assertEq(IERC20(wstETH).balanceOf(address(wallet)), 5e18); + assertEq(IMorpho(morpho).position(marketId(marketParams), address(wallet)).collateral, 5e18); + assertApproxEqAbs( + IMorpho(morpho).position(marketId(marketParams), address(wallet)).borrowShares, + SharesMathLib.toSharesUp(200e6, totalBorrowAssets, totalBorrowShares), + 1 + ); } function testSupplyCollateralAndBorrow() public { From c27fca73fdd019ba93bd140b5cf7536d2c7ddce9 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Tue, 20 Aug 2024 17:10:09 -0700 Subject: [PATCH 16/50] morpho stuff --- src/builder/Actions.sol | 12 +++ src/builder/MorphoWellKnown.sol | 34 ++++++ src/builder/QuarkBuilder.sol | 184 ++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 src/builder/MorphoWellKnown.sol diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index c6a28334..a3de223f 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -122,6 +122,18 @@ library Actions { uint256 blockTimestamp; } + struct MorphoBorrow { + Accounts.ChainAccounts[] chainAccountsList; + string assetSymbol; + uint256 amount; + uint256 chainId; + address borrower; + uint256 blockTimestamp; + uint256 collateralAmount; + string collateralAssetSymbol; + address comet; + } + // Note: Mainly to avoid stack too deep errors struct BridgeOperationInfo { string assetSymbol; diff --git a/src/builder/MorphoWellKnown.sol b/src/builder/MorphoWellKnown.sol new file mode 100644 index 00000000..8327cf81 --- /dev/null +++ b/src/builder/MorphoWellKnown.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.23; + +library MorphoWellKnown { + error UnsupportedChainId(); + + // Note: Current Morpho has same address across mainnet and base + function getMorphoAddress() internal pure returns (address) { + return 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + } + + // Note: Most Adaptive Curve IRM contracts has consistant address, but Morpho leaves it customizable + function getAdaptiveCurveIrmAddress(uint256 chainId) internal pure returns (address) { + if (chainId == 1) { + return 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC; // Ethereum + } else if (chainId == 8453) { + return 0x46415998764C29aB2a25CbeA6254146D50D22687; // Base + } else { + revert UnsupportedChainId(); + } + } + + struct MarketParams { + address loanToken; + address collateralToken; + address oracle; + address irm; + uint256 lltv; + } + + function getMarketParams(string memory borrowAssetSymbol, string memory collateralAssetSymbol) internal pure returns () { + + } +} \ No newline at end of file diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index d59bbed3..79337006 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -8,6 +8,7 @@ import {Accounts} from "./Accounts.sol"; import {BridgeRoutes} from "./BridgeRoutes.sol"; import {EIP712Helper} from "./EIP712Helper.sol"; import {Math} from "src/lib/Math.sol"; +import {MorphoWellKnown} from "./MorphoWellKnown.sol"; import {Strings} from "./Strings.sol"; import {PaycallWrapper} from "./PaycallWrapper.sol"; import {QuotecallWrapper} from "./QuotecallWrapper.sol"; @@ -982,6 +983,189 @@ contract QuarkBuilder { }); } + struct MorphoBorrowIntent { + uint256 amount; + string assetSymbol; + uint256 blockTimestamp; + address borrower; + uint256 chainId; + uint256 collateralAmount; + string collateralAssetSymbol; + string borrowAssetSymbol; + address oracle; + uint256 lltv; + } + + function morphoBorrow( + MorphoBorrowIntent memory borrowIntent, + Accounts.ChainAccounts[] memory chainAccountsList, + PaymentInfo.Payment memory payment + ) external pure returns (BuilderResult memory) { + List.DynamicArray memory actions = List.newList(); + List.DynamicArray memory quarkOperations = List.newList(); + + + bool useQuotecall = false; // never use Quotecall + bool paymentTokenIsCollateralAsset = false; + + for (uint256 i = 0; i < borrowIntent.collateralAssetSymbols.length; ++i) { + string memory assetSymbol = borrowIntent.collateralAssetSymbols[i]; + uint256 supplyAmount = borrowIntent.collateralAmounts[i]; + + assertFundsAvailable(borrowIntent.chainId, assetSymbol, supplyAmount, chainAccountsList, payment); + + if (Strings.stringEqIgnoreCase(assetSymbol, payment.currency)) { + paymentTokenIsCollateralAsset = true; + } + + if (needsBridgedFunds(assetSymbol, supplyAmount, borrowIntent.chainId, chainAccountsList, payment)) { + // Note: Assumes that the asset uses the same # of decimals on each chain + uint256 amountNeededOnDst = supplyAmount; + // If action is paid for with tokens and the payment token is + // the supply token, we need to add the max cost to the + // amountNeededOnDst for target chain + if (payment.isToken && Strings.stringEqIgnoreCase(payment.currency, assetSymbol)) { + amountNeededOnDst += PaymentInfo.findMaxCost(payment, borrowIntent.chainId); + } + (IQuarkWallet.QuarkOperation[] memory bridgeQuarkOperations, Actions.Action[] memory bridgeActions) = + Actions.constructBridgeOperations( + Actions.BridgeOperationInfo({ + assetSymbol: assetSymbol, + amountNeededOnDst: amountNeededOnDst, + dstChainId: borrowIntent.chainId, + recipient: borrowIntent.borrower, + blockTimestamp: borrowIntent.blockTimestamp, + useQuotecall: useQuotecall + }), + chainAccountsList, + payment + ); + + for (uint256 j = 0; j < bridgeQuarkOperations.length; ++j) { + List.addQuarkOperation(quarkOperations, bridgeQuarkOperations[j]); + List.addAction(actions, bridgeActions[j]); + } + } + } + + // when paying with tokens, you may need to bridge the payment token to cover the cost + if (payment.isToken && !paymentTokenIsCollateralAsset) { + uint256 maxCostOnDstChain = PaymentInfo.findMaxCost(payment, borrowIntent.chainId); + // but if you're borrowing the payment token, you can use the + // borrowed amount to cover the cost + + if (Strings.stringEqIgnoreCase(payment.currency, borrowIntent.assetSymbol)) { + maxCostOnDstChain = Math.subtractFlooredAtZero(maxCostOnDstChain, borrowIntent.amount); + } + + if ( + needsBridgedFunds(payment.currency, maxCostOnDstChain, borrowIntent.chainId, chainAccountsList, payment) + ) { + (IQuarkWallet.QuarkOperation[] memory bridgeQuarkOperations, Actions.Action[] memory bridgeActions) = + Actions.constructBridgeOperations( + Actions.BridgeOperationInfo({ + assetSymbol: payment.currency, + amountNeededOnDst: maxCostOnDstChain, + dstChainId: borrowIntent.chainId, + recipient: borrowIntent.borrower, + blockTimestamp: borrowIntent.blockTimestamp, + useQuotecall: useQuotecall + }), + chainAccountsList, + payment + ); + + for (uint256 i = 0; i < bridgeQuarkOperations.length; ++i) { + List.addQuarkOperation(quarkOperations, bridgeQuarkOperations[i]); + List.addAction(actions, bridgeActions[i]); + } + } + } + + // Auto-wrap on collateral supply + for (uint256 i = 0; i < borrowIntent.collateralAssetSymbols.length; ++i) { + checkAndInsertWrapOrUnwrapAction( + actions, + quarkOperations, + chainAccountsList, + payment, + borrowIntent.collateralAssetSymbols[i], + borrowIntent.collateralAmounts[i], + borrowIntent.chainId, + borrowIntent.borrower, + borrowIntent.blockTimestamp, + useQuotecall + ); + } + + (IQuarkWallet.QuarkOperation memory borrowQuarkOperation, Actions.Action memory borrowAction) = Actions + .cometBorrow( + Actions.CometBorrowInput({ + chainAccountsList: chainAccountsList, + amount: borrowIntent.amount, + assetSymbol: borrowIntent.assetSymbol, + blockTimestamp: borrowIntent.blockTimestamp, + borrower: borrowIntent.borrower, + chainId: borrowIntent.chainId, + collateralAmounts: borrowIntent.collateralAmounts, + collateralAssetSymbols: borrowIntent.collateralAssetSymbols, + comet: borrowIntent.comet + }), + payment + ); + + List.addQuarkOperation(quarkOperations, borrowQuarkOperation); + List.addAction(actions, borrowAction); + + // Convert actions and quark operations to arrays + Actions.Action[] memory actionsArray = List.toActionArray(actions); + IQuarkWallet.QuarkOperation[] memory quarkOperationsArray = List.toQuarkOperationArray(quarkOperations); + + // Validate generated actions for affordability + if (payment.isToken) { + uint256 supplementalPaymentTokenBalance = 0; + if (Strings.stringEqIgnoreCase(payment.currency, borrowIntent.assetSymbol)) { + supplementalPaymentTokenBalance += borrowIntent.amount; + } + + assertSufficientPaymentTokenBalances( + actionsArray, + chainAccountsList, + borrowIntent.chainId, + borrowIntent.borrower, + supplementalPaymentTokenBalance + ); + } + + // Merge operations that are from the same chain into one Multicall operation + (quarkOperationsArray, actionsArray) = + QuarkOperationHelper.mergeSameChainOperations(quarkOperationsArray, actionsArray); + + // Wrap operations around Paycall/Quotecall if payment is with token + if (payment.isToken) { + quarkOperationsArray = QuarkOperationHelper.wrapOperationsWithTokenPayment( + quarkOperationsArray, actionsArray, payment, useQuotecall + ); + } + + return BuilderResult({ + version: VERSION, + actions: actionsArray, + quarkOperations: quarkOperationsArray, + paymentCurrency: payment.currency, + eip712Data: EIP712Helper.eip712DataForQuarkOperations(quarkOperationsArray, actionsArray) + }); + } + + struct MorphoSupplyIntent { + uint256 amount; + string assetSymbol; + uint256 blockTimestamp; + uint256 chainId; + address vault; + address sender; + } + // For some reason, funds that may otherwise be bridgeable or held by the user cannot // be made available to fulfill the transaction. // Funds cannot be bridged, e.g. no bridge exists From 0ddbb348aa401e4a57255a84dd2a6f63d517d329 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 21 Aug 2024 17:24:10 -0700 Subject: [PATCH 17/50] Add MorphoInfo with hardcoded file, and simple test to verify the addresses set input in contract with Morpho system --- src/builder/MorphoInfo.sol | 330 ++++++++++++++++++ src/builder/MorphoWellKnown.sol | 34 -- .../MorphoInfo.t.sol | 89 +++++ 3 files changed, 419 insertions(+), 34 deletions(-) create mode 100644 src/builder/MorphoInfo.sol delete mode 100644 src/builder/MorphoWellKnown.sol create mode 100644 test/on_chain_info_verificaion/MorphoInfo.t.sol diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol new file mode 100644 index 00000000..a4cf73a4 --- /dev/null +++ b/src/builder/MorphoInfo.sol @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.23; + +import {MarketParams} from "src/interfaces/IMorpho.sol"; +import {HashMap} from "./HashMap.sol"; + +library MorphoInfo { + error UnsupportedChainId(); + + // Note: Current Morpho has same address across mainnet and base + function getMorphoAddress() internal pure returns (address) { + return 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + } + + function getKnownMorphoMarketsParams() internal pure returns (HashMap.Map memory) { + HashMap.Map memory knownMarkets = HashMap.newMap(); + // === Mainnet morpho markets === + // WBTC collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "USDC", collateralAssetSymbol: "WBTC"}), + MarketParams({ + loanToken: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, + collateralToken: 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, + oracle: 0xDddd770BADd886dF3864029e4B377B5F6a2B6b83, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "USDT", collateralAssetSymbol: "WBTC"}), + MarketParams({ + loanToken: 0xdAC17F958D2ee523a2206206994597C13D831ec7, + collateralToken: 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, + oracle: 0x008bF4B1cDA0cc9f0e882E0697f036667652E1ef, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "WETH", collateralAssetSymbol: "WBTC"}), + MarketParams({ + loanToken: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, + collateralToken: 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, + oracle: 0xc29B3Bc033640baE31ca53F8a0Eb892AdF68e663, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.915e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "PYUSD", collateralAssetSymbol: "WBTC"}), + MarketParams({ + loanToken: 0x6c3ea9036406852006290770BEdFcAbA0e23A0e8, + collateralToken: 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, + oracle: 0xc53c90d6E9A5B69E4ABf3d5Ae4c79225C7FeF3d2, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "eUSD", collateralAssetSymbol: "WBTC"}), + MarketParams({ + loanToken: 0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F, + collateralToken: 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, + oracle: 0x032F1C64899b2C89835E51aCeD9434b0aDEaA69d, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "USDA", collateralAssetSymbol: "WBTC"}), + MarketParams({ + loanToken: 0x0000206329b97DB379d5E1Bf586BbDB969C63274, + collateralToken: 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, + oracle: 0x032F1C64899b2C89835E51aCeD9434b0aDEaA69d, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + // wstETH collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "WETH", collateralAssetSymbol: "wstETH"}), + MarketParams({ + loanToken: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, + collateralToken: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, + oracle: 0xbD60A6770b27E084E8617335ddE769241B0e71D8, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.945e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "USDC", collateralAssetSymbol: "wstETH"}), + MarketParams({ + loanToken: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, + collateralToken: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, + oracle: 0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "USDC", collateralAssetSymbol: "wstETH"}), + MarketParams({ + loanToken: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, + collateralToken: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, + oracle: 0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "USDT", collateralAssetSymbol: "wstETH"}), + MarketParams({ + loanToken: 0xdAC17F958D2ee523a2206206994597C13D831ec7, + collateralToken: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, + oracle: 0x95DB30fAb9A3754e42423000DF27732CB2396992, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "eUSD", collateralAssetSymbol: "wstETH"}), + MarketParams({ + loanToken: 0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F, + collateralToken: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, + oracle: 0xBC693693fDBB177Ad05ff38633110016BC043AC5, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "PYUSD", collateralAssetSymbol: "wstETH"}), + MarketParams({ + loanToken: 0x6c3ea9036406852006290770BEdFcAbA0e23A0e8, + collateralToken: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, + oracle: 0x27679a17b7419fB10Bd9D143f21407760fdA5C53, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + // weETH collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "WETH", collateralAssetSymbol: "weETH"}), + MarketParams({ + loanToken: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, + collateralToken: 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee, + oracle: 0x3fa58b74e9a8eA8768eb33c8453e9C2Ed089A40a, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + // MKR collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "USDC", collateralAssetSymbol: "MKR"}), + MarketParams({ + loanToken: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, + collateralToken: 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2, + oracle: 0x6686788B4315A4F93d822c1Bf73910556FCe2d5a, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.77e18 + }) + ); + // USDe collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "DAI", collateralAssetSymbol: "USDe"}), + MarketParams({ + loanToken: 0x6B175474E89094C44Da98b954EedeAC495271d0F, + collateralToken: 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3, + oracle: 0xaE4750d0813B5E37A51f7629beedd72AF1f9cA35, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + // sUSDe collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 1, borrowAssetSymbol: "DAI", collateralAssetSymbol: "sUSDe"}), + MarketParams({ + loanToken: 0x6B175474E89094C44Da98b954EedeAC495271d0F, + collateralToken: 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497, + oracle: 0x5D916980D5Ae1737a8330Bf24dF812b2911Aae25, + irm: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC, + lltv: 0.86e18 + }) + ); + // === Base morpho markets === + // WETH collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 8453, borrowAssetSymbol: "USDC", collateralAssetSymbol: "WETH"}), + MarketParams({ + loanToken: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, + collateralToken: 0x4200000000000000000000000000000000000006, + oracle: 0xFEa2D58cEfCb9fcb597723c6bAE66fFE4193aFE4, + irm: 0x46415998764C29aB2a25CbeA6254146D50D22687, + lltv: 0.86e18 + }) + ); + // wstETH collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 8453, borrowAssetSymbol: "WETH", collateralAssetSymbol: "wstETH"}), + MarketParams({ + loanToken: 0x4200000000000000000000000000000000000006, + collateralToken: 0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452, + oracle: 0x4A11590e5326138B514E08A9B52202D42077Ca65, + irm: 0x46415998764C29aB2a25CbeA6254146D50D22687, + lltv: 0.945e18 + }) + ); + // cbETH collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 8453, borrowAssetSymbol: "USDC", collateralAssetSymbol: "cbETH"}), + MarketParams({ + loanToken: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, + collateralToken: 0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22, + oracle: 0xb40d93F44411D8C09aD17d7F88195eF9b05cCD96, + irm: 0x46415998764C29aB2a25CbeA6254146D50D22687, + lltv: 0.86e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 8453, borrowAssetSymbol: "WETH", collateralAssetSymbol: "cbETH"}), + MarketParams({ + loanToken: 0x4200000000000000000000000000000000000006, + collateralToken: 0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22, + oracle: 0xB03855Ad5AFD6B8db8091DD5551CAC4ed621d9E6, + irm: 0x46415998764C29aB2a25CbeA6254146D50D22687, + lltv: 0.945e18 + }) + ); + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 8453, borrowAssetSymbol: "eUSD", collateralAssetSymbol: "cbETH"}), + MarketParams({ + loanToken: 0xCfA3Ef56d303AE4fAabA0592388F19d7C3399FB4, + collateralToken: 0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22, + oracle: 0xc3Fa71D77d80f671F366DAA6812C8bD6C7749cEc, + irm: 0x46415998764C29aB2a25CbeA6254146D50D22687, + lltv: 0.86e18 + }) + ); + // ezETH collateral markets + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 8453, borrowAssetSymbol: "WETH", collateralAssetSymbol: "ezETH"}), + MarketParams({ + loanToken: 0x4200000000000000000000000000000000000006, + collateralToken: 0x2416092f143378750bb29b79eD961ab195CcEea5, + oracle: 0xcca88a97dE6700Bb5DAdf4082Cf35A55F383AF05, + irm: 0x46415998764C29aB2a25CbeA6254146D50D22687, + lltv: 0.915e18 + }) + ); + + // === Sepolia testnet morpho markets === + // None + + // === Base Sepolia testnet morpho markets === + addMarketParams( + knownMarkets, + MorphoMarketKey({chainId: 84532, borrowAssetSymbol: "USDC", collateralAssetSymbol: "WETH"}), + MarketParams({ + loanToken: 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + collateralToken: 0x4200000000000000000000000000000000000006, + oracle: 0x1631366C38d49ba58793A5F219050923fbF24C81, + irm: 0x46415998764C29aB2a25CbeA6254146D50D22687, + lltv: 0.915e18 + }) + ); + + return knownMarkets; + } + + function getMarketParams(uint256 chainId, string memory borrowAssetSymbol, string memory collateralAssetSymbol) + internal + pure + returns (MarketParams memory) + { + HashMap.Map memory knownMarkets = getKnownMorphoMarketsParams(); + return getMarketParams( + knownMarkets, + MorphoMarketKey({ + chainId: chainId, + borrowAssetSymbol: borrowAssetSymbol, + collateralAssetSymbol: collateralAssetSymbol + }) + ); + } + + // Helpers for map + + // Note: This is a simple key, as one market per chain per borrow asset per collateral asset + struct MorphoMarketKey { + uint256 chainId; + string borrowAssetSymbol; + string collateralAssetSymbol; + } + + function addMarketParams(HashMap.Map memory knownMarkets, MorphoMarketKey memory key, MarketParams memory params) + internal + pure + { + HashMap.put(knownMarkets, abi.encode(key), abi.encode(params)); + } + + function getMarketParams(HashMap.Map memory knownMarkets, MorphoMarketKey memory key) + internal + pure + returns (MarketParams memory) + { + return abi.decode(HashMap.get(knownMarkets, abi.encode(key)), (MarketParams)); + } +} diff --git a/src/builder/MorphoWellKnown.sol b/src/builder/MorphoWellKnown.sol deleted file mode 100644 index 8327cf81..00000000 --- a/src/builder/MorphoWellKnown.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; - -library MorphoWellKnown { - error UnsupportedChainId(); - - // Note: Current Morpho has same address across mainnet and base - function getMorphoAddress() internal pure returns (address) { - return 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; - } - - // Note: Most Adaptive Curve IRM contracts has consistant address, but Morpho leaves it customizable - function getAdaptiveCurveIrmAddress(uint256 chainId) internal pure returns (address) { - if (chainId == 1) { - return 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC; // Ethereum - } else if (chainId == 8453) { - return 0x46415998764C29aB2a25CbeA6254146D50D22687; // Base - } else { - revert UnsupportedChainId(); - } - } - - struct MarketParams { - address loanToken; - address collateralToken; - address oracle; - address irm; - uint256 lltv; - } - - function getMarketParams(string memory borrowAssetSymbol, string memory collateralAssetSymbol) internal pure returns () { - - } -} \ No newline at end of file diff --git a/test/on_chain_info_verificaion/MorphoInfo.t.sol b/test/on_chain_info_verificaion/MorphoInfo.t.sol new file mode 100644 index 00000000..ec0ff830 --- /dev/null +++ b/test/on_chain_info_verificaion/MorphoInfo.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "forge-std/StdUtils.sol"; +import "forge-std/StdMath.sol"; + +import {HashMap} from "src/builder/HashMap.sol"; +import {IMorpho, MarketParams} from "src/interfaces/IMorpho.sol"; +import {MorphoInfo} from "src/builder/MorphoInfo.sol"; +/** + * Verify the MorphoInfo info is correct on-chain + */ + +contract MorphoInfoTest is Test { + function testEthMainnet() public { + // Fork setup to get on-chain on eth mainnet + vm.createSelectFork( + string.concat( + "https://node-provider.compound.finance/ethereum-mainnet/", vm.envString("NODE_PROVIDER_BYPASS_KEY") + ), + 20580267 // 2024-08-21 16:27:00 PST + ); + + verifyKnownMarketsParams(1); + } + + function testBaseMainnet() public { + // Fork setup to get on-chain on base mainnet + vm.createSelectFork( + string.concat( + "https://node-provider.compound.finance/base-mainnet/", vm.envString("NODE_PROVIDER_BYPASS_KEY") + ), + 18746757 // 2024-08-21 16:27:00 PST + ); + + verifyKnownMarketsParams(8453); + } + + function testEthSepolia() public { + // Fork setup to get on-chain on base sepolia + vm.createSelectFork( + string.concat( + "https://node-provider.compound.finance/ethereum-sepolia/", vm.envString("NODE_PROVIDER_BYPASS_KEY") + ), + 6546096 // 2024-08-21 16:27:00 PST + ); + + verifyKnownMarketsParams(11155111); + } + + function testBaseSepolia() public { + // Fork setup to get on-chain on base sepolia + vm.createSelectFork( + string.concat( + "https://node-provider.compound.finance/base-sepolia/", vm.envString("NODE_PROVIDER_BYPASS_KEY") + ), + 14257289 // 2024-08-21 16:27:00 PST + ); + + verifyKnownMarketsParams(84532); + } + + function verifyKnownMarketsParams(uint256 chainId) internal { + HashMap.Map memory markets = MorphoInfo.getKnownMorphoMarketsParams(); + bytes[] memory keys = HashMap.keys(markets); + MorphoInfo.MorphoMarketKey[] memory marketKeys = new MorphoInfo.MorphoMarketKey[](keys.length); + for (uint256 i = 0; i < keys.length; ++i) { + marketKeys[i] = abi.decode(keys[i], (MorphoInfo.MorphoMarketKey)); + } + + // Filter and verify + for (uint256 i = 0; i < marketKeys.length; ++i) { + if (marketKeys[i].chainId == chainId) { + MarketParams memory marketParams = MorphoInfo.getMarketParams(markets, marketKeys[i]); + (uint128 totalSupplyAssets,,,,uint128 lastUpdate,) = + IMorpho(MorphoInfo.getMorphoAddress()).market(marketId(marketParams)); + assertGt(totalSupplyAssets, 0, "MorphoInfo has markets with NO liquidity, something is wrong and my impact user expereince"); + assertGt(lastUpdate, 0, "MorphoInfo has markets with NO lastUpdate, the market is never used"); + } + } + } + // Helper function to convert MarketParams to bytes32 Id + // Reference: https://github.com/morpho-org/morpho-blue/blob/731e3f7ed97cf15f8fe00b86e4be5365eb3802ac/src/libraries/MarketParamsLib.sol + function marketId(MarketParams memory params) public pure returns (bytes32) { + return keccak256(abi.encode(params)); + } +} From 9da3c015b731214fd302d1fbd3f2a251e3b48e17 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 21 Aug 2024 19:13:10 -0700 Subject: [PATCH 18/50] add known vaults (hand picked) --- src/builder/MorphoInfo.sol | 89 +++++++++++++++++-- .../MorphoInfo.t.sol | 38 +++++++- 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol index a4cf73a4..200dac14 100644 --- a/src/builder/MorphoInfo.sol +++ b/src/builder/MorphoInfo.sol @@ -12,6 +12,14 @@ library MorphoInfo { return 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; } + // Morpho blue markets + // Note: This is a simple key, as one market per chain per borrow asset per collateral asset + struct MorphoMarketKey { + uint256 chainId; + string borrowAssetSymbol; + string collateralAssetSymbol; + } + function getKnownMorphoMarketsParams() internal pure returns (HashMap.Map memory) { HashMap.Map memory knownMarkets = HashMap.newMap(); // === Mainnet morpho markets === @@ -304,13 +312,84 @@ library MorphoInfo { ); } + // Morpho vaults + // Note: Potentiall can add other key (i.e. curator) for supporting multiple vaults with same assets + struct MorphoVaultKey { + uint256 chainId; + string supplyAssetSymbol; + } + + function getKnownMorphoVaultsAddresses() internal pure returns (HashMap.Map memory) { + HashMap.Map memory knownVaults = HashMap.newMap(); + // === Mainnet morpho vaults === + // USDC (Gauntlet USDC Core) + addMorphoVaultAddress( + knownVaults, + MorphoVaultKey({chainId: 1, supplyAssetSymbol: "USDC"}), + 0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458 + ); + // USDT (Gaunlet USDT Prime) + addMorphoVaultAddress( + knownVaults, + MorphoVaultKey({chainId: 1, supplyAssetSymbol: "USDT"}), + 0x8CB3649114051cA5119141a34C200D65dc0Faa73 + ); + // WETH (Gauntlet WETH Core) + addMorphoVaultAddress( + knownVaults, + MorphoVaultKey({chainId: 1, supplyAssetSymbol: "WETH"}), + 0x4881Ef0BF6d2365D3dd6499ccd7532bcdBCE0658 + ); + // WBTC (Guantlet WBTC Core) + addMorphoVaultAddress( + knownVaults, + MorphoVaultKey({chainId: 1, supplyAssetSymbol: "WBTC"}), + 0x443df5eEE3196e9b2Dd77CaBd3eA76C3dee8f9b2 + ); + + // === Base morpho vaults === + // USDC (Moonwell Flaghship USDC) + addMorphoVaultAddress( + knownVaults, + MorphoVaultKey({chainId: 8453, supplyAssetSymbol: "USDC"}), + 0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca + ); + // WETH (Moonwell Flaghship ETH) + addMorphoVaultAddress( + knownVaults, + MorphoVaultKey({chainId: 8453, supplyAssetSymbol: "WETH"}), + 0xa0E430870c4604CcfC7B38Ca7845B1FF653D0ff1 + ); + + // === Sepolia testnet morpho vaults === + // None + + // === Base Sepolia testnet morpho vaults === + // None + + return knownVaults; + } + + function getMorphoVaultAddress(uint256 chainId, string memory supplyAssetSymbol) internal pure returns (address) { + HashMap.Map memory knownVaults = getKnownMorphoVaultsAddresses(); + return + getMorphoVaultAddress(knownVaults, MorphoVaultKey({chainId: chainId, supplyAssetSymbol: supplyAssetSymbol})); + } + // Helpers for map + function addMorphoVaultAddress(HashMap.Map memory knownVaults, MorphoVaultKey memory key, address addr) + internal + pure + { + HashMap.put(knownVaults, abi.encode(key), abi.encode(addr)); + } - // Note: This is a simple key, as one market per chain per borrow asset per collateral asset - struct MorphoMarketKey { - uint256 chainId; - string borrowAssetSymbol; - string collateralAssetSymbol; + function getMorphoVaultAddress(HashMap.Map memory knownVaults, MorphoVaultKey memory key) + internal + pure + returns (address) + { + return abi.decode(HashMap.get(knownVaults, abi.encode(key)), (address)); } function addMarketParams(HashMap.Map memory knownMarkets, MorphoMarketKey memory key, MarketParams memory params) diff --git a/test/on_chain_info_verificaion/MorphoInfo.t.sol b/test/on_chain_info_verificaion/MorphoInfo.t.sol index ec0ff830..4651ff7b 100644 --- a/test/on_chain_info_verificaion/MorphoInfo.t.sol +++ b/test/on_chain_info_verificaion/MorphoInfo.t.sol @@ -8,11 +8,13 @@ import "forge-std/StdMath.sol"; import {HashMap} from "src/builder/HashMap.sol"; import {IMorpho, MarketParams} from "src/interfaces/IMorpho.sol"; +import {IMetaMorpho} from "src/interfaces/IMetaMorpho.sol"; import {MorphoInfo} from "src/builder/MorphoInfo.sol"; +import {IERC4626} from "openzeppelin/interfaces/IERC4626.sol"; + /** * Verify the MorphoInfo info is correct on-chain */ - contract MorphoInfoTest is Test { function testEthMainnet() public { // Fork setup to get on-chain on eth mainnet @@ -24,6 +26,7 @@ contract MorphoInfoTest is Test { ); verifyKnownMarketsParams(1); + verifyKnownVaults(1); } function testBaseMainnet() public { @@ -36,6 +39,7 @@ contract MorphoInfoTest is Test { ); verifyKnownMarketsParams(8453); + verifyKnownVaults(8453); } function testEthSepolia() public { @@ -48,6 +52,7 @@ contract MorphoInfoTest is Test { ); verifyKnownMarketsParams(11155111); + verifyKnownVaults(11155111); } function testBaseSepolia() public { @@ -60,6 +65,7 @@ contract MorphoInfoTest is Test { ); verifyKnownMarketsParams(84532); + verifyKnownVaults(84532); } function verifyKnownMarketsParams(uint256 chainId) internal { @@ -74,13 +80,39 @@ contract MorphoInfoTest is Test { for (uint256 i = 0; i < marketKeys.length; ++i) { if (marketKeys[i].chainId == chainId) { MarketParams memory marketParams = MorphoInfo.getMarketParams(markets, marketKeys[i]); - (uint128 totalSupplyAssets,,,,uint128 lastUpdate,) = + (uint128 totalSupplyAssets,,,, uint128 lastUpdate,) = IMorpho(MorphoInfo.getMorphoAddress()).market(marketId(marketParams)); - assertGt(totalSupplyAssets, 0, "MorphoInfo has markets with NO liquidity, something is wrong and my impact user expereince"); + assertGt( + totalSupplyAssets, + 0, + "MorphoInfo has markets with NO liquidity, something is wrong and may impact user expereince" + ); assertGt(lastUpdate, 0, "MorphoInfo has markets with NO lastUpdate, the market is never used"); } } } + + function verifyKnownVaults(uint256 chainId) internal { + HashMap.Map memory vaults = MorphoInfo.getKnownMorphoVaultsAddresses(); + bytes[] memory keys = HashMap.keys(vaults); + MorphoInfo.MorphoVaultKey[] memory vaultKeys = new MorphoInfo.MorphoVaultKey[](keys.length); + for (uint256 i = 0; i < keys.length; ++i) { + vaultKeys[i] = abi.decode(keys[i], (MorphoInfo.MorphoVaultKey)); + } + + // Filter and verify + for (uint256 i = 0; i < vaultKeys.length; ++i) { + if (vaultKeys[i].chainId == chainId) { + address vault = MorphoInfo.getMorphoVaultAddress(vaults, vaultKeys[i]); + assertGt( + IERC4626(vault).totalAssets(), + 0, + "MorphoInfo has vaults with NO assets, empty vault may impact user expereince" + ); + } + } + } + // Helper function to convert MarketParams to bytes32 Id // Reference: https://github.com/morpho-org/morpho-blue/blob/731e3f7ed97cf15f8fe00b86e4be5365eb3802ac/src/libraries/MarketParamsLib.sol function marketId(MarketParams memory params) public pure returns (bytes32) { From 41216831df9adf7f959f0c7662c717652972ddd3 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 22 Aug 2024 17:43:13 -0700 Subject: [PATCH 19/50] migrated account info, and migrated all the dependencies :sweat: --- src/builder/Accounts.sol | 21 ++ src/builder/Actions.sol | 257 ++++++++++++++++--- src/builder/MorphoInfo.sol | 6 + src/builder/QuarkBuilder.sol | 126 +++++---- test/builder/QuarkBuilderCometBorrow.t.sol | 60 +++-- test/builder/QuarkBuilderCometRepay.t.sol | 70 +++-- test/builder/QuarkBuilderCometSupply.t.sol | 6 +- test/builder/QuarkBuilderCometWithdraw.t.sol | 12 +- test/builder/QuarkBuilderSwap.t.sol | 12 +- test/builder/QuarkBuilderTransfer.t.sol | 30 ++- test/builder/lib/QuarkBuilderTest.sol | 62 ++++- 11 files changed, 501 insertions(+), 161 deletions(-) diff --git a/src/builder/Accounts.sol b/src/builder/Accounts.sol index ee72e70e..44eef26f 100644 --- a/src/builder/Accounts.sol +++ b/src/builder/Accounts.sol @@ -13,6 +13,7 @@ library Accounts { QuarkState[] quarkStates; AssetPositions[] assetPositionsList; CometPositions[] cometPositions; + MorphoBluePositions [] morphoBluePositions; } // We map this to the Portfolio data structure that the client will already have. @@ -57,6 +58,26 @@ library Accounts { uint256[] balances; } + struct MorphoBluePositions { + bytes32 marketId; + address morpho; + address loanToken; + address collateralToken; + MorphoBlueBorrowPosition borrowPosition; + MorphoBlueCollateralPosition collateralPosition; + } + + struct MorphoBlueBorrowPosition { + address[] accounts; + uint256[] borrowedBalances; + uint256[] borrowedShares; + } + + struct MorphoBlueCollateralPosition { + address[] accounts; + uint256[] collateralBalances; + } + function findChainAccounts(uint256 chainId, ChainAccounts[] memory chainAccountsList) internal pure diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index a3de223f..6d6c3c53 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -12,13 +12,17 @@ import { CometSupplyActions, CometSupplyMultipleAssetsAndBorrow, CometWithdrawActions, - TransferActions + TransferActions, + MorphoBlueActions, + MorphoRewardsActions, + MorphoVaultActions } from "../DeFiScripts.sol"; import {WrapperActions} from "../WrapperScripts.sol"; - import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; +import {IMorpho, Position} from "../interfaces/IMorpho.sol"; import {PaymentInfo} from "./PaymentInfo.sol"; import {TokenWrapper} from "./TokenWrapper.sol"; +import {MorphoInfo} from "./MorphoInfo.sol"; import {List} from "./List.sol"; library Actions { @@ -122,6 +126,30 @@ library Actions { uint256 blockTimestamp; } + struct CometBorrowInput { + Accounts.ChainAccounts[] chainAccountsList; + uint256 amount; + string assetSymbol; + uint256 blockTimestamp; + address borrower; + uint256 chainId; + uint256[] collateralAmounts; + string[] collateralAssetSymbols; + address comet; + } + + struct CometRepayInput { + Accounts.ChainAccounts[] chainAccountsList; + uint256 amount; + string assetSymbol; + uint256 blockTimestamp; + uint256 chainId; + uint256[] collateralAmounts; + string[] collateralAssetSymbols; + address comet; + address repayer; + } + struct MorphoBorrow { Accounts.ChainAccounts[] chainAccountsList; string assetSymbol; @@ -131,7 +159,19 @@ library Actions { uint256 blockTimestamp; uint256 collateralAmount; string collateralAssetSymbol; - address comet; + } + + struct MorphoRepay { + Accounts.ChainAccounts[] chainAccountsList; + string assetSymbol; + uint256 amount; + uint256 chainId; + address repayer; + uint256 blockTimestamp; + uint256 collateralAmount; + string collateralAssetSymbol; + // Needed for handling max repay + uint256 borrowedShares; } // Note: Mainly to avoid stack too deep errors @@ -173,6 +213,8 @@ library Actions { address comet; uint256 price; address token; + address morpho; + bytes32 morphoMarketId; } struct BridgeActionContext { @@ -209,6 +251,8 @@ library Actions { address comet; uint256 price; address token; + address morpho; + bytes32 morphoMarketId; } struct SupplyActionContext { @@ -482,18 +526,6 @@ library Actions { return (quarkOperation, action); } - struct CometBorrowInput { - Accounts.ChainAccounts[] chainAccountsList; - uint256 amount; - string assetSymbol; - uint256 blockTimestamp; - address borrower; - uint256 chainId; - uint256[] collateralAmounts; - string[] collateralAssetSymbols; - address comet; - } - function cometBorrow(CometBorrowInput memory borrowInput, PaymentInfo.Payment memory payment) internal pure @@ -540,7 +572,7 @@ library Actions { }); // Construct Action - BorrowActionContext memory repayActionContext = BorrowActionContext({ + BorrowActionContext memory borrowActionContext = BorrowActionContext({ assetSymbol: borrowInput.assetSymbol, amount: borrowInput.amount, chainId: borrowInput.chainId, @@ -550,13 +582,15 @@ library Actions { collateralAssetSymbols: borrowInput.collateralAssetSymbols, comet: borrowInput.comet, price: borrowAssetPositions.usdPrice, - token: borrowAssetPositions.asset + token: borrowAssetPositions.asset, + morpho: address(0), + morphoMarketId: bytes32(0) }); Action memory action = Actions.Action({ chainId: borrowInput.chainId, quarkAccount: borrowInput.borrower, actionType: ACTION_TYPE_BORROW, - actionContext: abi.encode(repayActionContext), + actionContext: abi.encode(borrowActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), // Null address for OFFCHAIN payment. paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : address(0), @@ -567,18 +601,6 @@ library Actions { return (quarkOperation, action); } - struct CometRepayInput { - Accounts.ChainAccounts[] chainAccountsList; - uint256 amount; - string assetSymbol; - uint256 blockTimestamp; - uint256 chainId; - uint256[] collateralAmounts; - string[] collateralAssetSymbols; - address comet; - address repayer; - } - function cometRepay(CometRepayInput memory repayInput, PaymentInfo.Payment memory payment) internal pure @@ -635,7 +657,9 @@ library Actions { collateralTokens: collateralTokens, comet: repayInput.comet, price: repayAssetPositions.usdPrice, - token: repayAssetPositions.asset + token: repayAssetPositions.asset, + morpho: address(0), + morphoMarketId: bytes32(0) }); Action memory action = Actions.Action({ chainId: repayInput.chainId, @@ -831,6 +855,177 @@ library Actions { return (quarkOperation, action); } + function morphoBorrow(MorphoBorrow memory borrowInput, PaymentInfo.Payment memory payment) + internal + pure + returns (IQuarkWallet.QuarkOperation memory, Action memory) + { + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = type(MorphoBlueActions).creationCode; + + Accounts.ChainAccounts memory accounts = + Accounts.findChainAccounts(borrowInput.chainId, borrowInput.chainAccountsList); + + Accounts.QuarkState memory accountState = Accounts.findQuarkState(borrowInput.borrower, accounts.quarkStates); + + Accounts.AssetPositions memory borrowAssetPositions = + Accounts.findAssetPositions(borrowInput.assetSymbol, accounts.assetPositionsList); + + Accounts.AssetPositions memory assetPositions = + Accounts.findAssetPositions(borrowInput.collateralAssetSymbol, accounts.assetPositionsList); + + bytes memory scriptCalldata = abi.encodeWithSelector( + MorphoBlueActions.supplyCollateralAndBorrow.selector, + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.assetSymbol, borrowInput.collateralAssetSymbol), + borrowInput.collateralAmount, + borrowInput.amount, + borrowInput.borrower, + borrowInput.borrower + ); + + // Construct QuarkOperation + IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ + nonce: accountState.quarkNextNonce, + scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode), + scriptCalldata: scriptCalldata, + scriptSources: scriptSources, + expiry: borrowInput.blockTimestamp + STANDARD_EXPIRY_BUFFER + }); + + // Construct Action + + // Single element arrays to fit to existing context format + uint256[] memory collateralAmountsArray = new uint256[](1); + collateralAmountsArray[0] = borrowInput.collateralAmount; + uint256[] memory collateralTokenPricesArray = new uint256[](1); + collateralTokenPricesArray[0] = assetPositions.usdPrice; + address[] memory collateralTokensArray = new address[](1); + collateralTokensArray[0] = assetPositions.asset; + string[] memory collateralAssetSymbolsArray = new string[](1); + collateralAssetSymbolsArray[0] = borrowInput.collateralAssetSymbol; + + BorrowActionContext memory borrowActionContext = BorrowActionContext({ + assetSymbol: borrowInput.assetSymbol, + amount: borrowInput.amount, + chainId: borrowInput.chainId, + collateralAmounts: collateralAmountsArray, + collateralTokenPrices: collateralTokenPricesArray, + collateralTokens: collateralTokensArray, + collateralAssetSymbols: collateralAssetSymbolsArray, + comet: address(0), + price: borrowAssetPositions.usdPrice, + token: borrowAssetPositions.asset, + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId( + MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.assetSymbol, borrowInput.collateralAssetSymbol) + ) + }); + Action memory action = Actions.Action({ + chainId: borrowInput.chainId, + quarkAccount: borrowInput.borrower, + actionType: ACTION_TYPE_BORROW, + actionContext: abi.encode(borrowActionContext), + paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), + // Null address for OFFCHAIN payment. + paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : address(0), + paymentTokenSymbol: payment.currency, + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0 + }); + + return (quarkOperation, action); + } + + function morphoRepay(MorphoRepay memory repayInput, PaymentInfo.Payment memory payment) + internal + pure + returns (IQuarkWallet.QuarkOperation memory, Action memory) + { + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = type(MorphoBlueActions).creationCode; + + Accounts.ChainAccounts memory accounts = + Accounts.findChainAccounts(repayInput.chainId, repayInput.chainAccountsList); + + Accounts.QuarkState memory accountState = Accounts.findQuarkState(repayInput.repayer, accounts.quarkStates); + + Accounts.AssetPositions memory repayAssetPositions = + Accounts.findAssetPositions(repayInput.assetSymbol, accounts.assetPositionsList); + + Accounts.AssetPositions memory assetPositions = + Accounts.findAssetPositions(repayInput.collateralAssetSymbol, accounts.assetPositionsList); + + uint256 repayAmount = repayInput.amount; + uint256 repayShares = 0; + + if (repayInput.amount == type(uint256).max) { + repayAmount = 0; + repayShares = repayInput.borrowedShares; + } + bytes memory scriptCalldata = abi.encodeWithSelector( + MorphoBlueActions.repayAndWithdrawCollateral.selector, + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(repayInput.chainId, repayInput.assetSymbol, repayInput.collateralAssetSymbol), + repayAmount, + repayShares, + repayInput.collateralAmount, + repayInput.repayer, + repayInput.repayer + ); + + // Construct QuarkOperation + IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ + nonce: accountState.quarkNextNonce, + scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode), + scriptCalldata: scriptCalldata, + scriptSources: scriptSources, + expiry: repayInput.blockTimestamp + STANDARD_EXPIRY_BUFFER + }); + + // Construct Action + + // Single element arrays to fit to existing context format + uint256[] memory collateralAmountsArray = new uint256[](1); + collateralAmountsArray[0] = repayInput.collateralAmount; + string[] memory collateralAssetSymbolsArray = new string[](1); + collateralAssetSymbolsArray[0] = repayInput.collateralAssetSymbol; + uint256[] memory collateralTokenPricesArray = new uint256[](1); + collateralTokenPricesArray[0] = assetPositions.usdPrice; + address[] memory collateralTokensArray = new address[](1); + collateralTokensArray[0] = assetPositions.asset; + + RepayActionContext memory repayActionContext = RepayActionContext({ + amount: repayInput.amount, + assetSymbol: repayInput.assetSymbol, + chainId: repayInput.chainId, + collateralAmounts: collateralAmountsArray, + collateralAssetSymbols: collateralAssetSymbolsArray, + collateralTokenPrices: collateralTokenPricesArray, + collateralTokens: collateralTokensArray, + comet: address(0), + price: repayAssetPositions.usdPrice, + token: repayAssetPositions.asset, + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId( + MorphoInfo.getMarketParams(repayInput.chainId, repayInput.assetSymbol, repayInput.collateralAssetSymbol) + ) + }); + + Action memory action = Actions.Action({ + chainId: repayInput.chainId, + quarkAccount: repayInput.repayer, + actionType: ACTION_TYPE_REPAY, + actionContext: abi.encode(repayActionContext), + paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), + // Null address for OFFCHAIN payment. + paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token : address(0), + paymentTokenSymbol: payment.currency, + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0 + }); + + return (quarkOperation, action); + } + function wrapOrUnwrapAsset( WrapOrUnwrapAsset memory wrapOrUnwrap, PaymentInfo.Payment memory payment, diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol index 200dac14..5d6c8775 100644 --- a/src/builder/MorphoInfo.sol +++ b/src/builder/MorphoInfo.sol @@ -406,4 +406,10 @@ library MorphoInfo { { return abi.decode(HashMap.get(knownMarkets, abi.encode(key)), (MarketParams)); } + + // Helper function to convert MarketParams to bytes32 Id + // Reference: https://github.com/morpho-org/morpho-blue/blob/731e3f7ed97cf15f8fe00b86e4be5365eb3802ac/src/libraries/MarketParamsLib.sol + function marketId(MarketParams memory params) public pure returns (bytes32) { + return keccak256(abi.encode(params)); + } } diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index 79337006..ea753987 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -8,7 +8,7 @@ import {Accounts} from "./Accounts.sol"; import {BridgeRoutes} from "./BridgeRoutes.sol"; import {EIP712Helper} from "./EIP712Helper.sol"; import {Math} from "src/lib/Math.sol"; -import {MorphoWellKnown} from "./MorphoWellKnown.sol"; +import {MorphoInfo} from "./MorphoInfo.sol"; import {Strings} from "./Strings.sol"; import {PaycallWrapper} from "./PaycallWrapper.sol"; import {QuotecallWrapper} from "./QuotecallWrapper.sol"; @@ -992,8 +992,6 @@ contract QuarkBuilder { uint256 collateralAmount; string collateralAssetSymbol; string borrowAssetSymbol; - address oracle; - uint256 lltv; } function morphoBorrow( @@ -1004,47 +1002,51 @@ contract QuarkBuilder { List.DynamicArray memory actions = List.newList(); List.DynamicArray memory quarkOperations = List.newList(); - bool useQuotecall = false; // never use Quotecall bool paymentTokenIsCollateralAsset = false; - for (uint256 i = 0; i < borrowIntent.collateralAssetSymbols.length; ++i) { - string memory assetSymbol = borrowIntent.collateralAssetSymbols[i]; - uint256 supplyAmount = borrowIntent.collateralAmounts[i]; + assertFundsAvailable( + borrowIntent.chainId, + borrowIntent.collateralAssetSymbol, + borrowIntent.collateralAmount, + chainAccountsList, + payment + ); - assertFundsAvailable(borrowIntent.chainId, assetSymbol, supplyAmount, chainAccountsList, payment); + if (Strings.stringEqIgnoreCase(borrowIntent.collateralAssetSymbol, payment.currency)) { + paymentTokenIsCollateralAsset = true; + } - if (Strings.stringEqIgnoreCase(assetSymbol, payment.currency)) { - paymentTokenIsCollateralAsset = true; + if ( + needsBridgedFunds( + borrowIntent.collateralAssetSymbol, + borrowIntent.collateralAmount, + borrowIntent.chainId, + chainAccountsList, + payment + ) + ) { + uint256 amountNeededOnDst = borrowIntent.collateralAmount; + if (payment.isToken && Strings.stringEqIgnoreCase(payment.currency, borrowIntent.collateralAssetSymbol)) { + amountNeededOnDst += PaymentInfo.findMaxCost(payment, borrowIntent.chainId); } + (IQuarkWallet.QuarkOperation[] memory bridgeQuarkOperations, Actions.Action[] memory bridgeActions) = + Actions.constructBridgeOperations( + Actions.BridgeOperationInfo({ + assetSymbol: borrowIntent.collateralAssetSymbol, + amountNeededOnDst: amountNeededOnDst, + dstChainId: borrowIntent.chainId, + recipient: borrowIntent.borrower, + blockTimestamp: borrowIntent.blockTimestamp, + useQuotecall: useQuotecall + }), + chainAccountsList, + payment + ); - if (needsBridgedFunds(assetSymbol, supplyAmount, borrowIntent.chainId, chainAccountsList, payment)) { - // Note: Assumes that the asset uses the same # of decimals on each chain - uint256 amountNeededOnDst = supplyAmount; - // If action is paid for with tokens and the payment token is - // the supply token, we need to add the max cost to the - // amountNeededOnDst for target chain - if (payment.isToken && Strings.stringEqIgnoreCase(payment.currency, assetSymbol)) { - amountNeededOnDst += PaymentInfo.findMaxCost(payment, borrowIntent.chainId); - } - (IQuarkWallet.QuarkOperation[] memory bridgeQuarkOperations, Actions.Action[] memory bridgeActions) = - Actions.constructBridgeOperations( - Actions.BridgeOperationInfo({ - assetSymbol: assetSymbol, - amountNeededOnDst: amountNeededOnDst, - dstChainId: borrowIntent.chainId, - recipient: borrowIntent.borrower, - blockTimestamp: borrowIntent.blockTimestamp, - useQuotecall: useQuotecall - }), - chainAccountsList, - payment - ); - - for (uint256 j = 0; j < bridgeQuarkOperations.length; ++j) { - List.addQuarkOperation(quarkOperations, bridgeQuarkOperations[j]); - List.addAction(actions, bridgeActions[j]); - } + for (uint256 j = 0; j < bridgeQuarkOperations.length; ++j) { + List.addQuarkOperation(quarkOperations, bridgeQuarkOperations[j]); + List.addAction(actions, bridgeActions[j]); } } @@ -1083,33 +1085,30 @@ contract QuarkBuilder { } // Auto-wrap on collateral supply - for (uint256 i = 0; i < borrowIntent.collateralAssetSymbols.length; ++i) { - checkAndInsertWrapOrUnwrapAction( - actions, - quarkOperations, - chainAccountsList, - payment, - borrowIntent.collateralAssetSymbols[i], - borrowIntent.collateralAmounts[i], - borrowIntent.chainId, - borrowIntent.borrower, - borrowIntent.blockTimestamp, - useQuotecall - ); - } + checkAndInsertWrapOrUnwrapAction( + actions, + quarkOperations, + chainAccountsList, + payment, + borrowIntent.collateralAssetSymbol, + borrowIntent.collateralAmount, + borrowIntent.chainId, + borrowIntent.borrower, + borrowIntent.blockTimestamp, + useQuotecall + ); (IQuarkWallet.QuarkOperation memory borrowQuarkOperation, Actions.Action memory borrowAction) = Actions - .cometBorrow( - Actions.CometBorrowInput({ + .morphoBorrow( + Actions.MorphoBorrow({ chainAccountsList: chainAccountsList, - amount: borrowIntent.amount, assetSymbol: borrowIntent.assetSymbol, - blockTimestamp: borrowIntent.blockTimestamp, - borrower: borrowIntent.borrower, + amount: borrowIntent.amount, chainId: borrowIntent.chainId, - collateralAmounts: borrowIntent.collateralAmounts, - collateralAssetSymbols: borrowIntent.collateralAssetSymbols, - comet: borrowIntent.comet + borrower: borrowIntent.borrower, + blockTimestamp: borrowIntent.blockTimestamp, + collateralAmount: borrowIntent.collateralAmount, + collateralAssetSymbol: borrowIntent.collateralAssetSymbol }), payment ); @@ -1157,15 +1156,6 @@ contract QuarkBuilder { }); } - struct MorphoSupplyIntent { - uint256 amount; - string assetSymbol; - uint256 blockTimestamp; - uint256 chainId; - address vault; - address sender; - } - // For some reason, funds that may otherwise be bridgeable or held by the user cannot // be made available to fulfill the transaction. // Funds cannot be bridged, e.g. no bridge exists diff --git a/test/builder/QuarkBuilderCometBorrow.t.sol b/test/builder/QuarkBuilderCometBorrow.t.sol index 981e2172..95235310 100644 --- a/test/builder/QuarkBuilderCometBorrow.t.sol +++ b/test/builder/QuarkBuilderCometBorrow.t.sol @@ -87,7 +87,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -95,7 +96,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -176,7 +178,9 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1) + token: usdc_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from BorrowActionContext" @@ -208,7 +212,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 10e18, 0, 0), // user has 10 ETH - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -216,7 +221,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -289,7 +295,9 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1) + token: usdc_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from BorrowActionContext" @@ -315,7 +323,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 10e18, 0), // user has 1 USDC, 10 LINK - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -323,7 +332,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -411,7 +421,9 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1) + token: usdc_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from BorrowActionContext" @@ -441,7 +453,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK and 0 USDC - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -449,7 +462,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometBorrow( @@ -532,7 +546,9 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1) + token: usdc_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from BorrowActionContext" @@ -564,7 +580,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(3e6, 0, 0, 0), // 3 USDC on mainnet - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -572,7 +589,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 5e18, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometBorrow( @@ -716,7 +734,9 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDT_PRICE, - token: usdt_(8453) + token: usdt_(8453), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from BorrowActionContext" @@ -748,7 +768,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -756,7 +777,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometBorrow( @@ -900,7 +922,9 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: WETH_PRICE, - token: weth_(8453) + token: weth_(8453), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from BorrowActionContext" diff --git a/test/builder/QuarkBuilderCometRepay.t.sol b/test/builder/QuarkBuilderCometRepay.t.sol index 47740d48..2e89fc87 100644 --- a/test/builder/QuarkBuilderCometRepay.t.sol +++ b/test/builder/QuarkBuilderCometRepay.t.sol @@ -86,7 +86,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 0, 1e18), // user does not have enough USDC - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); vm.expectRevert(QuarkBuilder.MaxCostTooHigh.selector); @@ -112,7 +113,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -120,7 +122,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -202,7 +205,9 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1) + token: usdc_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from RepayActionContext" @@ -234,7 +239,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 1e18, 0, 0), // has 1 ETH - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -242,7 +248,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -317,7 +324,9 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometWeth_(1), price: WETH_PRICE, - token: weth_(1) + token: weth_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from RepayActionContext" @@ -343,7 +352,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(2e6, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -351,7 +361,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -440,7 +451,9 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1) + token: usdc_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from RepayActionContext" @@ -471,7 +484,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 1e18), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -479,7 +493,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometRepay( @@ -564,7 +579,9 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometWeth_(1), price: WETH_PRICE, - token: weth_(1) + token: weth_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from RepayActionContext" @@ -596,7 +613,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -604,7 +622,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 2, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometRepay( @@ -749,7 +768,9 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(8453), price: USDC_PRICE, - token: usdc_(8453) + token: usdc_(8453), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from RepayActionContext" @@ -785,7 +806,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC - cometPortfolios: cometPortfolios + cometPortfolios: cometPortfolios, + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -870,7 +892,9 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1) + token: usdc_(1), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from RepayActionContext" @@ -907,7 +931,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC on mainnet - cometPortfolios: emptyCometPortfolios_() + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -915,7 +940,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base - cometPortfolios: cometPortfolios + cometPortfolios: cometPortfolios, + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -1060,7 +1086,9 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(8453), price: USDC_PRICE, - token: usdc_(8453) + token: usdc_(8453), + morpho: address(0), + morphoMarketId: bytes32(0) }) ), "action context encoded from RepayActionContext" diff --git a/test/builder/QuarkBuilderCometSupply.t.sol b/test/builder/QuarkBuilderCometSupply.t.sol index 51fc298a..5ed073da 100644 --- a/test/builder/QuarkBuilderCometSupply.t.sol +++ b/test/builder/QuarkBuilderCometSupply.t.sol @@ -158,7 +158,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(3e6)), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); QuarkBuilder.BuilderResult memory result = builder.cometSupply( @@ -260,7 +261,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); QuarkBuilder.BuilderResult memory result = diff --git a/test/builder/QuarkBuilderCometWithdraw.t.sol b/test/builder/QuarkBuilderCometWithdraw.t.sol index 39434404..624831f7 100644 --- a/test/builder/QuarkBuilderCometWithdraw.t.sol +++ b/test/builder/QuarkBuilderCometWithdraw.t.sol @@ -255,13 +255,15 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 3e6), // 3 USDC on mainnet - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 0), // 0 USDC on base - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); QuarkBuilder.BuilderResult memory result = builder.cometWithdraw( @@ -396,7 +398,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: cometPortfolios + cometPortfolios: cometPortfolios, + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -483,7 +486,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), - cometPortfolios: cometPortfolios + cometPortfolios: cometPortfolios, + morphoBluePortfolios: emptyMorphoBluePortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); diff --git a/test/builder/QuarkBuilderSwap.t.sol b/test/builder/QuarkBuilderSwap.t.sol index b6d198ca..ba15b29c 100644 --- a/test/builder/QuarkBuilderSwap.t.sol +++ b/test/builder/QuarkBuilderSwap.t.sol @@ -248,7 +248,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); QuarkBuilder.BuilderResult memory result = builder.swap( buyUsdc_(1, weth_(1), 1e18, 3000e6, address(0xa11ce), BLOCK_TIMESTAMP), // swap 1 ETH on chain 1 to 3000 USDC @@ -405,19 +406,22 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(9005e6)), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), uint256(0)), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, quarkStates: quarkStates_(address(0xc0b), 5), assetPositionsList: assetPositionsList_(7777, address(0xc0b), uint256(0)), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); QuarkBuilder.BuilderResult memory result = builder.swap( diff --git a/test/builder/QuarkBuilderTransfer.t.sol b/test/builder/QuarkBuilderTransfer.t.sol index 8f0f241f..f80fcf21 100644 --- a/test/builder/QuarkBuilderTransfer.t.sol +++ b/test/builder/QuarkBuilderTransfer.t.sol @@ -609,7 +609,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(10e6)), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); QuarkBuilder.BuilderResult memory result = builder.transfer( @@ -685,13 +686,15 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 8e6), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 4e6), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); QuarkBuilder.BuilderResult memory result = builder.transfer( @@ -820,19 +823,22 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 8e6), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 4e6), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, quarkStates: quarkStates_(address(0xfe11a), 2), assetPositionsList: assetPositionsList_(7777, address(0xfe11a), 5e6), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); // User has total holding of 17 USDC, but only 12 USDC is available for transfer/bridge to 8453, and missing 5 USDC stuck in random L2 so will revert with FundsUnavailable error @@ -919,7 +925,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); // Transfer 1.5ETH to 0xceecee on chain 1 @@ -1020,7 +1027,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); // Transfer 1.5ETH to 0xceecee on chain 1 @@ -1129,7 +1137,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); // Transfer max ETH to 0xceecee on chain 1 @@ -1239,7 +1248,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); // Transfer 1.5ETH to 0xceecee on chain 1 diff --git a/test/builder/lib/QuarkBuilderTest.sol b/test/builder/lib/QuarkBuilderTest.sol index 9c2e2398..5e749987 100644 --- a/test/builder/lib/QuarkBuilderTest.sol +++ b/test/builder/lib/QuarkBuilderTest.sol @@ -84,19 +84,22 @@ contract QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(amount / 2)), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), uint256(amount / 2)), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, quarkStates: quarkStates_(address(0xc0b), 5), assetPositionsList: assetPositionsList_(7777, address(0xc0b), uint256(0)), - cometPositions: emptyCometPositions_() + cometPositions: emptyCometPositions_(), + morphoBluePositions: emptyMorphoBluePositions_() }); return chainAccountsList; } @@ -106,6 +109,11 @@ contract QuarkBuilderTest { return emptyCometPositions; } + function emptyMorphoBluePositions_() internal pure returns (Accounts.MorphoBluePositions[] memory) { + Accounts.MorphoBluePositions[] memory emptyMorphoBluePositions = new Accounts.MorphoBluePositions[](0); + return emptyMorphoBluePositions; + } + function quarkStates_() internal pure returns (Accounts.QuarkState[] memory) { Accounts.QuarkState[] memory quarkStates = new Accounts.QuarkState[](1); quarkStates[0] = quarkState_(); @@ -266,6 +274,7 @@ contract QuarkBuilderTest { string[] assetSymbols; uint256[] assetBalances; CometPortfolio[] cometPortfolios; + MorphoBluePortfolio[] morphoBluePortfolios; } struct CometPortfolio { @@ -276,11 +285,26 @@ contract QuarkBuilderTest { uint256[] collateralAssetBalances; } + struct MorphoBluePortfolio { + bytes32 marketId; + address morpho; + address loanToken; + address collateralToken; + uint256 borrowedBalance; + uint256 borrowedShares; + uint256 collateralBalance; + } + function emptyCometPortfolios_() internal pure returns (CometPortfolio[] memory) { CometPortfolio[] memory emptyCometPortfolios = new CometPortfolio[](0); return emptyCometPortfolios; } + function emptyMorphoBluePortfolios_() internal pure returns (MorphoBluePortfolio[] memory) { + MorphoBluePortfolio[] memory emptyMorphoBluePortfolios = new MorphoBluePortfolio[](0); + return emptyMorphoBluePortfolios; + } + function chainAccountsFromChainPortfolios(ChainPortfolio[] memory chainPortfolios) internal pure @@ -300,6 +324,9 @@ contract QuarkBuilderTest { // cometPositions: cometPositionsFor cometPositions: cometPositionsForCometPorfolios( chainPortfolios[i].chainId, chainPortfolios[i].account, chainPortfolios[i].cometPortfolios + ), + morphoBluePositions: morphoBluePositionsForMorphoBluePortfolios( + chainPortfolios[i].chainId, chainPortfolios[i].account, chainPortfolios[i].morphoBluePortfolios ) }); } @@ -343,6 +370,35 @@ contract QuarkBuilderTest { return cometPositions; } + function morphoBluePositionsForMorphoBluePortfolios( + uint256 chainId, + address account, + MorphoBluePortfolio[] memory morphoBluePortfolios + ) internal pure returns (Accounts.MorphoBluePositions[] memory) { + Accounts.MorphoBluePositions[] memory morphoBluePositions = new Accounts.MorphoBluePositions[](morphoBluePortfolios.length); + + for (uint256 i = 0; i < morphoBluePortfolios.length; ++i) { + MorphoBluePortfolio memory morphoBluePortfolio = morphoBluePortfolios[i]; + morphoBluePositions[i] = Accounts.MorphoBluePositions({ + marketId: morphoBluePortfolio.marketId, + morpho: morphoBluePortfolio.morpho, + loanToken: morphoBluePortfolio.loanToken, + collateralToken: morphoBluePortfolio.collateralToken, + borrowPosition: Accounts.MorphoBlueBorrowPosition({ + accounts: Arrays.addressArray(account), + borrowedBalances: Arrays.uintArray(morphoBluePortfolio.borrowedBalance), + borrowedShares: Arrays.uintArray(morphoBluePortfolio.borrowedShares) + }), + collateralPosition: Accounts.MorphoBlueCollateralPosition({ + accounts: Arrays.addressArray(account), + collateralBalances: Arrays.uintArray(morphoBluePortfolio.collateralBalance) + }) + }); + } + + return morphoBluePositions; + } + function baseAssetForComet(uint256 chainId, address comet) internal pure returns (address) { if (comet == COMET_1_USDC || comet == COMET_8453_USDC) { return usdc_(chainId); From 5940d6bfa24dd35167f8727ea2ff55195099d320 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Fri, 23 Aug 2024 11:31:32 -0700 Subject: [PATCH 20/50] repay and borrow --- src/builder/Accounts.sol | 36 ++++- src/builder/Actions.sol | 3 +- src/builder/QuarkBuilder.sol | 163 +++++++++++++++++++++ test/builder/QuarkBuilderCometBorrow.t.sol | 2 +- test/builder/QuarkBuilderTransfer.t.sol | 2 +- test/builder/lib/QuarkBuilderTest.sol | 23 +-- 6 files changed, 215 insertions(+), 14 deletions(-) diff --git a/src/builder/Accounts.sol b/src/builder/Accounts.sol index 44eef26f..4630e2f0 100644 --- a/src/builder/Accounts.sol +++ b/src/builder/Accounts.sol @@ -13,7 +13,7 @@ library Accounts { QuarkState[] quarkStates; AssetPositions[] assetPositionsList; CometPositions[] cometPositions; - MorphoBluePositions [] morphoBluePositions; + MorphoBluePositions[] morphoBluePositions; } // We map this to the Portfolio data structure that the client will already have. @@ -103,6 +103,23 @@ library Accounts { } } + function findMorphoBluePositions( + uint256 chainId, + address loanToken, + address collateralToken, + ChainAccounts[] memory chainAccountsList + ) internal pure returns (MorphoBluePositions memory found) { + ChainAccounts memory chainAccounts = findChainAccounts(chainId, chainAccountsList); + for (uint256 i = 0; i < chainAccounts.morphoBluePositions.length; ++i) { + if ( + chainAccounts.morphoBluePositions[i].loanToken == loanToken + && chainAccounts.morphoBluePositions[i].collateralToken == collateralToken + ) { + return found = chainAccounts.morphoBluePositions[i]; + } + } + } + function findAssetPositions(string memory assetSymbol, AssetPositions[] memory assetPositionsList) internal pure @@ -274,4 +291,21 @@ library Accounts { } } } + + function totalMorphoBorrowForAccount( + Accounts.ChainAccounts[] memory chainAccountsList, + uint256 chainId, + address loanToken, + address collateralToken, + address account + ) internal pure returns (uint256 totalBorrow, uint256 totalBorrowedShares) { + Accounts.MorphoBluePositions memory morphoBluePositions = + findMorphoBluePositions(chainId, loanToken, collateralToken, chainAccountsList); + for (uint256 i = 0; i < morphoBluePositions.borrowPosition.accounts.length; ++i) { + if (morphoBluePositions.borrowPosition.accounts[i] == account) { + totalBorrow = morphoBluePositions.borrowPosition.borrowedBalances[i]; + totalBorrowedShares = morphoBluePositions.borrowPosition.borrowedShares[i]; + } + } + } } diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 6d6c3c53..45966f2a 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -171,6 +171,7 @@ library Actions { uint256 collateralAmount; string collateralAssetSymbol; // Needed for handling max repay + bool payInShares; uint256 borrowedShares; } @@ -958,7 +959,7 @@ library Actions { uint256 repayAmount = repayInput.amount; uint256 repayShares = 0; - if (repayInput.amount == type(uint256).max) { + if (repayInput.payInShares) { repayAmount = 0; repayShares = repayInput.borrowedShares; } diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index ea753987..fe5661f8 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -75,6 +75,20 @@ contract QuarkBuilder { return totalBorrowForAccount + buffer; } + function morphorRepayMaxAmount( + Accounts.ChainAccounts[] memory chainAccountsList, + uint256 chainId, + address loanToken, + address collateralToken, + address repayer + ) internal pure returns (uint256 amount, uint256 shares) { + (uint256 totalBorrowForAccount, uint256 totalBorrowedSharesForAccount) = + Accounts.totalMorphoBorrowForAccount(chainAccountsList, chainId, loanToken, collateralToken, repayer); + uint256 buffer = totalBorrowForAccount / 1000; // 0.1% + amount = totalBorrowForAccount + buffer; + shares = totalBorrowedSharesForAccount; + } + function cometRepay( CometRepayIntent memory repayIntent, Accounts.ChainAccounts[] memory chainAccountsList, @@ -1156,6 +1170,155 @@ contract QuarkBuilder { }); } + struct MorphoRepayIntent { + uint256 amount; + string assetSymbol; + uint256 blockTimestamp; + address repayer; + uint256 chainId; + uint256 collateralAmount; + string collateralAssetSymbol; + } + + function morphoRepay( + MorphoRepayIntent memory repayIntent, + Accounts.ChainAccounts[] memory chainAccountsList, + PaymentInfo.Payment memory payment + ) external pure returns (BuilderResult memory) { + bool isMaxRepay = repayIntent.amount == type(uint256).max; + bool useQuotecall = false; // never use Quotecall + + // Note: Need to get the token address of the repay and collateral tokens to find the right morpho market position info (i.e. total borrowed amount and shares) + Accounts.AssetPositions memory loanTokenAssetPosition = + Accounts.findAssetPositions(repayIntent.assetSymbol, repayIntent.chainId, chainAccountsList); + Accounts.AssetPositions memory collateralTokenAssetPosition = + Accounts.findAssetPositions(repayIntent.collateralAssetSymbol, repayIntent.chainId, chainAccountsList); + + uint256 repayAmount; + uint256 repayShares; + if (isMaxRepay) { + (repayAmount, repayShares) = morphorRepayMaxAmount( + chainAccountsList, + repayIntent.chainId, + loanTokenAssetPosition.asset, + collateralTokenAssetPosition.asset, + repayIntent.repayer + ); + } else { + repayAmount = repayIntent.amount; + repayShares = 0; + } + + assertFundsAvailable(repayIntent.chainId, repayIntent.assetSymbol, repayAmount, chainAccountsList, payment); + + List.DynamicArray memory actions = List.newList(); + List.DynamicArray memory quarkOperations = List.newList(); + + if (needsBridgedFunds(repayIntent.assetSymbol, repayAmount, repayIntent.chainId, chainAccountsList, payment)) { + // Note: Assumes that the asset uses the same # of decimals on each chain + uint256 amountNeededOnDst = repayAmount; + // If action is paid for with tokens and the payment token is the + // repay token, we need to add the max cost to the + // amountNeededOnDst for target chain + if (payment.isToken && Strings.stringEqIgnoreCase(payment.currency, repayIntent.assetSymbol)) { + amountNeededOnDst += PaymentInfo.findMaxCost(payment, repayIntent.chainId); + } + (IQuarkWallet.QuarkOperation[] memory bridgeQuarkOperations, Actions.Action[] memory bridgeActions) = + Actions.constructBridgeOperations( + Actions.BridgeOperationInfo({ + assetSymbol: repayIntent.assetSymbol, + amountNeededOnDst: amountNeededOnDst, + dstChainId: repayIntent.chainId, + recipient: repayIntent.repayer, + blockTimestamp: repayIntent.blockTimestamp, + useQuotecall: useQuotecall + }), + chainAccountsList, + payment + ); + + for (uint256 i = 0; i < bridgeQuarkOperations.length; ++i) { + List.addAction(actions, bridgeActions[i]); + List.addQuarkOperation(quarkOperations, bridgeQuarkOperations[i]); + } + } + + // Auto-wrap + checkAndInsertWrapOrUnwrapAction( + actions, + quarkOperations, + chainAccountsList, + payment, + repayIntent.assetSymbol, + repayAmount, + repayIntent.chainId, + repayIntent.repayer, + repayIntent.blockTimestamp, + useQuotecall + ); + + (IQuarkWallet.QuarkOperation memory repayQuarkOperations, Actions.Action memory repayActions) = Actions + .morphoRepay( + Actions.MorphoRepay({ + chainAccountsList: chainAccountsList, + assetSymbol: repayIntent.assetSymbol, + amount: repayIntent.amount, + chainId: repayIntent.chainId, + repayer: repayIntent.repayer, + blockTimestamp: repayIntent.blockTimestamp, + collateralAmount: repayIntent.collateralAmount, + collateralAssetSymbol: repayIntent.collateralAssetSymbol, + payInShares: isMaxRepay, + borrowedShares: repayShares + }), + payment + ); + + List.addAction(actions, repayActions); + List.addQuarkOperation(quarkOperations, repayQuarkOperations); + + // Convert actions and quark operations to arrays + Actions.Action[] memory actionsArray = List.toActionArray(actions); + IQuarkWallet.QuarkOperation[] memory quarkOperationsArray = List.toQuarkOperationArray(quarkOperations); + + // Validate generated actions for affordability + if (payment.isToken) { + // if you are withdrawing the payment token, you can pay with the + // withdrawn funds + uint256 supplementalPaymentTokenBalance = 0; + if (Strings.stringEqIgnoreCase(payment.currency, repayIntent.collateralAssetSymbol)) { + supplementalPaymentTokenBalance += repayIntent.collateralAmount; + } + + assertSufficientPaymentTokenBalances( + actionsArray, + chainAccountsList, + repayIntent.chainId, + repayIntent.repayer, + supplementalPaymentTokenBalance + ); + } + + // Merge operations that are from the same chain into one Multicall operation + (quarkOperationsArray, actionsArray) = + QuarkOperationHelper.mergeSameChainOperations(quarkOperationsArray, actionsArray); + + // Wrap operations around Paycall/Quotecall if payment is with token + if (payment.isToken) { + quarkOperationsArray = QuarkOperationHelper.wrapOperationsWithTokenPayment( + quarkOperationsArray, actionsArray, payment, useQuotecall + ); + } + + return BuilderResult({ + version: VERSION, + actions: actionsArray, + quarkOperations: quarkOperationsArray, + paymentCurrency: payment.currency, + eip712Data: EIP712Helper.eip712DataForQuarkOperations(quarkOperationsArray, actionsArray) + }); + } + // For some reason, funds that may otherwise be bridgeable or held by the user cannot // be made available to fulfill the transaction. // Funds cannot be bridged, e.g. no bridge exists diff --git a/test/builder/QuarkBuilderCometBorrow.t.sol b/test/builder/QuarkBuilderCometBorrow.t.sol index 95235310..439b449f 100644 --- a/test/builder/QuarkBuilderCometBorrow.t.sol +++ b/test/builder/QuarkBuilderCometBorrow.t.sol @@ -87,7 +87,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { nextNonce: 12, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK - cometPortfolios: emptyCometPortfolios_(), + cometPortfolios: emptyCometPortfolios_(), morphoBluePortfolios: emptyMorphoBluePortfolios_() }); chainPortfolios[1] = ChainPortfolio({ diff --git a/test/builder/QuarkBuilderTransfer.t.sol b/test/builder/QuarkBuilderTransfer.t.sol index f80fcf21..3cd69895 100644 --- a/test/builder/QuarkBuilderTransfer.t.sol +++ b/test/builder/QuarkBuilderTransfer.t.sol @@ -1248,7 +1248,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, - cometPositions: emptyCometPositions_(), + cometPositions: emptyCometPositions_(), morphoBluePositions: emptyMorphoBluePositions_() }); diff --git a/test/builder/lib/QuarkBuilderTest.sol b/test/builder/lib/QuarkBuilderTest.sol index 5e749987..19b33232 100644 --- a/test/builder/lib/QuarkBuilderTest.sol +++ b/test/builder/lib/QuarkBuilderTest.sol @@ -11,7 +11,7 @@ import {Quotecall} from "src/Quotecall.sol"; import {PaymentInfo} from "src/builder/PaymentInfo.sol"; import {QuarkBuilder} from "src/builder/QuarkBuilder.sol"; import {Strings} from "src/builder/Strings.sol"; - +import {MorphoInfo} from "src/builder/MorphoInfo.sol"; import {Arrays} from "test/builder/lib/Arrays.sol"; contract QuarkBuilderTest { @@ -84,7 +84,7 @@ contract QuarkBuilderTest { chainId: 1, quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(amount / 2)), - cometPositions: emptyCometPositions_(), + cometPositions: emptyCometPositions_(), morphoBluePositions: emptyMorphoBluePositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ @@ -287,9 +287,8 @@ contract QuarkBuilderTest { struct MorphoBluePortfolio { bytes32 marketId; - address morpho; - address loanToken; - address collateralToken; + string loanToken; + string collateralToken; uint256 borrowedBalance; uint256 borrowedShares; uint256 collateralBalance; @@ -324,7 +323,7 @@ contract QuarkBuilderTest { // cometPositions: cometPositionsFor cometPositions: cometPositionsForCometPorfolios( chainPortfolios[i].chainId, chainPortfolios[i].account, chainPortfolios[i].cometPortfolios - ), + ), morphoBluePositions: morphoBluePositionsForMorphoBluePortfolios( chainPortfolios[i].chainId, chainPortfolios[i].account, chainPortfolios[i].morphoBluePortfolios ) @@ -375,15 +374,19 @@ contract QuarkBuilderTest { address account, MorphoBluePortfolio[] memory morphoBluePortfolios ) internal pure returns (Accounts.MorphoBluePositions[] memory) { - Accounts.MorphoBluePositions[] memory morphoBluePositions = new Accounts.MorphoBluePositions[](morphoBluePortfolios.length); + Accounts.MorphoBluePositions[] memory morphoBluePositions = + new Accounts.MorphoBluePositions[](morphoBluePortfolios.length); for (uint256 i = 0; i < morphoBluePortfolios.length; ++i) { MorphoBluePortfolio memory morphoBluePortfolio = morphoBluePortfolios[i]; + (address loanAsset,,) = assetInfo(morphoBluePortfolio.loanToken, chainId); + (address collateralAsset,,) = assetInfo(morphoBluePortfolio.collateralToken, chainId); + morphoBluePositions[i] = Accounts.MorphoBluePositions({ marketId: morphoBluePortfolio.marketId, - morpho: morphoBluePortfolio.morpho, - loanToken: morphoBluePortfolio.loanToken, - collateralToken: morphoBluePortfolio.collateralToken, + morpho: MorphoInfo.getMorphoAddress(), + loanToken: loanAsset, + collateralToken: collateralAsset, borrowPosition: Accounts.MorphoBlueBorrowPosition({ accounts: Arrays.addressArray(account), borrowedBalances: Arrays.uintArray(morphoBluePortfolio.borrowedBalance), From 3f01dff25e18215513b177cc4d3691cf16bc7185 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 26 Aug 2024 17:40:42 -0700 Subject: [PATCH 21/50] morpho borrow tests set --- src/builder/Actions.sol | 8 +- src/builder/MorphoInfo.sol | 10 +- src/builder/QuarkBuilder.sol | 1 - test/builder/QuarkBuilderMorphoBorrow.t.sol | 729 ++++++++++++++++++ test/builder/QuarkBuilderMorphoRepay.t.sol | 19 + test/builder/lib/QuarkBuilderTest.sol | 30 +- .../MorphoInfo.t.sol | 8 +- 7 files changed, 791 insertions(+), 14 deletions(-) create mode 100644 test/builder/QuarkBuilderMorphoBorrow.t.sol create mode 100644 test/builder/QuarkBuilderMorphoRepay.t.sol diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 45966f2a..77706b7a 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -878,7 +878,7 @@ library Actions { bytes memory scriptCalldata = abi.encodeWithSelector( MorphoBlueActions.supplyCollateralAndBorrow.selector, MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.assetSymbol, borrowInput.collateralAssetSymbol), + MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.collateralAssetSymbol, borrowInput.assetSymbol), borrowInput.collateralAmount, borrowInput.amount, borrowInput.borrower, @@ -919,7 +919,7 @@ library Actions { token: borrowAssetPositions.asset, morpho: MorphoInfo.getMorphoAddress(), morphoMarketId: MorphoInfo.marketId( - MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.assetSymbol, borrowInput.collateralAssetSymbol) + MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.collateralAssetSymbol, borrowInput.assetSymbol) ) }); Action memory action = Actions.Action({ @@ -966,7 +966,7 @@ library Actions { bytes memory scriptCalldata = abi.encodeWithSelector( MorphoBlueActions.repayAndWithdrawCollateral.selector, MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(repayInput.chainId, repayInput.assetSymbol, repayInput.collateralAssetSymbol), + MorphoInfo.getMarketParams(repayInput.chainId, repayInput.collateralAssetSymbol, repayInput.assetSymbol), repayAmount, repayShares, repayInput.collateralAmount, @@ -1008,7 +1008,7 @@ library Actions { token: repayAssetPositions.asset, morpho: MorphoInfo.getMorphoAddress(), morphoMarketId: MorphoInfo.marketId( - MorphoInfo.getMarketParams(repayInput.chainId, repayInput.assetSymbol, repayInput.collateralAssetSymbol) + MorphoInfo.getMarketParams(repayInput.chainId, repayInput.collateralAssetSymbol, repayInput.assetSymbol) ) }); diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol index 5d6c8775..95c3623a 100644 --- a/src/builder/MorphoInfo.sol +++ b/src/builder/MorphoInfo.sol @@ -6,6 +6,8 @@ import {HashMap} from "./HashMap.sol"; library MorphoInfo { error UnsupportedChainId(); + error MorphoMarketNotFound(); + error MorphoVaultNotFound(); // Note: Current Morpho has same address across mainnet and base function getMorphoAddress() internal pure returns (address) { @@ -296,7 +298,7 @@ library MorphoInfo { return knownMarkets; } - function getMarketParams(uint256 chainId, string memory borrowAssetSymbol, string memory collateralAssetSymbol) + function getMarketParams(uint256 chainId, string memory collateralAssetSymbol, string memory borrowAssetSymbol) internal pure returns (MarketParams memory) @@ -389,6 +391,9 @@ library MorphoInfo { pure returns (address) { + if (!HashMap.contains(knownVaults, abi.encode(key))) { + revert MorphoVaultNotFound(); + } return abi.decode(HashMap.get(knownVaults, abi.encode(key)), (address)); } @@ -404,6 +409,9 @@ library MorphoInfo { pure returns (MarketParams memory) { + if (!HashMap.contains(knownMarkets, abi.encode(key))) { + revert MorphoMarketNotFound(); + } return abi.decode(HashMap.get(knownMarkets, abi.encode(key)), (MarketParams)); } diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index fe5661f8..63e5304f 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1005,7 +1005,6 @@ contract QuarkBuilder { uint256 chainId; uint256 collateralAmount; string collateralAssetSymbol; - string borrowAssetSymbol; } function morphoBorrow( diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol new file mode 100644 index 00000000..d92198e7 --- /dev/null +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -0,0 +1,729 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.23; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {Arrays} from "test/builder/lib/Arrays.sol"; +import {QuarkBuilderTest, Accounts, PaymentInfo, QuarkBuilder} from "test/builder/lib/QuarkBuilderTest.sol"; + +import {Actions} from "src/builder/Actions.sol"; +import {CCTPBridgeActions} from "src/BridgeScripts.sol"; +import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; +import {MorphoBlueActions} from "src/DeFiScripts.sol"; +import {Paycall} from "src/Paycall.sol"; +import {Strings} from "src/builder/Strings.sol"; +import {Multicall} from "src/Multicall.sol"; +import {WrapperActions} from "src/WrapperScripts.sol"; +import {MorphoInfo} from "src/builder/MorphoInfo.sol"; +import {TokenWrapper} from "src/builder/TokenWrapper.sol"; + +contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { + function borrowIntent_( + uint256 chainId, + string memory assetSymbol, + uint256 amount, + string memory collateralAssetSymbol, + uint256 collateralAmount + ) internal pure returns (QuarkBuilder.MorphoBorrowIntent memory) { + return QuarkBuilder.MorphoBorrowIntent({ + amount: amount, + assetSymbol: assetSymbol, + blockTimestamp: BLOCK_TIMESTAMP, + borrower: address(0xa11ce), + chainId: chainId, + collateralAmount: collateralAmount, + collateralAssetSymbol: collateralAssetSymbol + }); + } + + function testBorrowInvalidMarketParams() public { + QuarkBuilder builder = new QuarkBuilder(); + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 1e8, 1e18), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + // Pair not exist in known Morpho markets + vm.expectRevert(MorphoInfo.MorphoMarketNotFound.selector); + builder.morphoBorrow( + borrowIntent_(1, "USDC", 1e6, "WETH", 1e18), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsd_() + ); + } + + function testBorrowFundsUnavailable() public { + QuarkBuilder builder = new QuarkBuilder(); + vm.expectRevert(abi.encodeWithSelector(QuarkBuilder.FundsUnavailable.selector, "WBTC", 1e8, 0)); + builder.morphoBorrow( + borrowIntent_(1, "USDC", 1e6, "WBTC", 1e8), + chainAccountsList_(3e6), // holding 3 USDC in total across chains 1, 8453 + paymentUsd_() + ); + } + + function testBorrowSuccess() public { + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 1e8, 0), // user has 1 WBTC + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoBorrow( + borrowIntent_(1, "USDC", 1e6, "WBTC", 1e8), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsd_() + ); + + assertEq(result.paymentCurrency, "usd", "usd currency"); + + // Check the quark operations + assertEq(result.quarkOperations.length, 1, "one operation"); + assertEq( + result.quarkOperations[0].scriptAddress, + address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + /* codeJar address */ + address(CodeJarHelper.CODE_JAR_ADDRESS), + uint256(0), + /* script bytecode */ + keccak256(type(MorphoBlueActions).creationCode) + ) + ) + ) + ) + ), + "script address is correct given the code jar address on mainnet" + ); + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeCall( + MorphoBlueActions.supplyCollateralAndBorrow, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(1, "WBTC", "USDC"), + 1e8, + 1e6, + address(0xa11ce), + address(0xa11ce) + ) + ), + "calldata is MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xal1ce), address(0xal1ce));" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 1); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + assertEq(result.actions.length, 1, "one action"); + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); + assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); + assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 1e8; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WBTC_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = wbtc_(1); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WBTC"; + + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.BorrowActionContext({ + amount: 1e6, + assetSymbol: "USDC", + chainId: 1, + collateralAmounts: collateralAmounts, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + collateralAssetSymbols: collateralAssetSymbols, + comet: address(0), + price: USDC_PRICE, + token: usdc_(1), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) + }) + ), + "action context encoded from BorrowActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testBorrowWithAutoWrapper() public { + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xa11ce), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), + assetBalances: Arrays.uintArray(0, 10e18, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoBorrow( + borrowIntent_(8453, "USDC", 1e6, "WETH", 1e18), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsd_() + ); + + assertEq(result.paymentCurrency, "usd", "usd currency"); + + address multicallAddress = CodeJarHelper.getCodeAddress(type(Multicall).creationCode); + address wrapperActionsAddress = CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode); + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + // Check the quark operations + assertEq(result.quarkOperations.length, 1, "one merged operation"); + assertEq( + result.quarkOperations[0].scriptAddress, + multicallAddress, + "script address is correct given the code jar address on mainnet" + ); + address[] memory callContracts = new address[](2); + callContracts[0] = wrapperActionsAddress; + callContracts[1] = morphoBlueActionsAddress; + bytes[] memory callDatas = new bytes[](2); + callDatas[0] = abi.encodeWithSelector( + WrapperActions.wrapETH.selector, TokenWrapper.getKnownWrapperTokenPair(8453, "WETH").wrapper, 1e18 + ); + callDatas[1] = abi.encodeCall( + MorphoBlueActions.supplyCollateralAndBorrow, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(8453, "WETH", "USDC"), + 1e18, + 1e6, + address(0xa11ce), + address(0xa11ce) + ) + ); + + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), + "calldata is Multicall.run([wrapperActionsAddress, morphoBlueActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 10e18), MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 1e6, address(0xa11ce), address(0xa11ce))" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 3); + assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[1], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[2], type(Multicall).creationCode); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + assertEq(result.actions.length, 1, "one action"); + assertEq(result.actions[0].chainId, 8453, "operation is on chainid 8453"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); + assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); + assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 1e18; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WETH_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = weth_(8453); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WETH"; + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.BorrowActionContext({ + amount: 1e6, + assetSymbol: "USDC", + chainId: 8453, + collateralAmounts: collateralAmounts, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + collateralAssetSymbols: collateralAssetSymbols, + comet: address(0), + price: USDC_PRICE, + token: usdc_(8453), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "WETH", "USDC")) + }) + ), + "action context encoded from BorrowActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testBorrowWithPaycall() public { + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(1e6, 0, 1e8, 0), // user has 1 WBTC and 1USDC for payment + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoBorrow( + borrowIntent_(1, "USDC", 1e6, "WBTC", 1e8), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) + ); + + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address paycallAddress = paycallUsdc_(1); + + assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // Check the quark operations + assertEq(result.quarkOperations.length, 1, "one operation"); + assertEq( + result.quarkOperations[0].scriptAddress, + paycallAddress, + "script address is correct given the code jar address on mainnet" + ); + + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + morphoBlueActionsAddress, + abi.encodeCall( + MorphoBlueActions.supplyCollateralAndBorrow, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(1, "WBTC", "USDC"), + 1e8, + 1e6, + address(0xa11ce), + address(0xa11ce) + ) + ), + 0.1e6 + ), + "calldata is Paycall.run(MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xa11ce), address(0xa11ce));" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 2); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[0].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + assertEq(result.actions.length, 1, "one action"); + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); + assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 1e8; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WBTC_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = wbtc_(1); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WBTC"; + + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.BorrowActionContext({ + amount: 1e6, + assetSymbol: "USDC", + chainId: 1, + collateralAmounts: collateralAmounts, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + collateralAssetSymbols: collateralAssetSymbols, + comet: address(0), + price: USDC_PRICE, + token: usdc_(1), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) + }) + ), + "action context encoded from BorrowActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testBorrowPayFromBorrow() public { + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 1e8, 0), // user has 1 WBTC but with 0 USDC + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoBorrow( + borrowIntent_(1, "USDC", 1e6, "WBTC", 1e8), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) + ); + + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address paycallAddress = paycallUsdc_(1); + + assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // Check the quark operations + assertEq(result.quarkOperations.length, 1, "one operation"); + assertEq( + result.quarkOperations[0].scriptAddress, + paycallAddress, + "script address is correct given the code jar address on mainnet" + ); + + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + morphoBlueActionsAddress, + abi.encodeCall( + MorphoBlueActions.supplyCollateralAndBorrow, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(1, "WBTC", "USDC"), + 1e8, + 1e6, + address(0xa11ce), + address(0xa11ce) + ) + ), + 0.1e6 + ), + "calldata is Paycall.run(MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xa11ce), address(0xa11ce));" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 2); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[0].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + assertEq(result.actions.length, 1, "one action"); + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); + assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 1e8; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WBTC_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = wbtc_(1); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WBTC"; + + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.BorrowActionContext({ + amount: 1e6, + assetSymbol: "USDC", + chainId: 1, + collateralAmounts: collateralAmounts, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + collateralAssetSymbols: collateralAssetSymbols, + comet: address(0), + price: USDC_PRICE, + token: usdc_(1), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) + }) + ), + "action context encoded from BorrowActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testBorrowWithBridgedPaymentToken() public { + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "cbETH", "WETH"), + assetBalances: Arrays.uintArray(5e6, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "cbETH", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 1e18, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](2); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 1e6}); // max cost on base is 1 USDC + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoBorrow( + borrowIntent_(8453, "WETH", 0.2e18, "cbETH", 1e18), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) + ); + + address paycallAddress = paycallUsdc_(1); + address paycallAddressBase = paycallUsdc_(8453); + address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + + assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // Check the quark operations + // first operation + assertEq(result.quarkOperations.length, 2, "two operations"); + assertEq( + result.quarkOperations[0].scriptAddress, + paycallAddress, + "script address is correct given the code jar address on base" + ); + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + cctpBridgeActionsAddress, + abi.encodeWithSelector( + CCTPBridgeActions.bridgeUSDC.selector, + address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), + 1e6, + 6, + bytes32(uint256(uint160(0xa11ce))), + usdc_(1) + ), + 0.1e6 + ), + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 2); + assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); + assertEq( + result.quarkOperations[0].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // second operation + assertEq( + result.quarkOperations[1].scriptAddress, + paycallAddressBase, + "script address[1] has been wrapped with paycall address" + ); + + assertEq( + result.quarkOperations[1].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + morphoBlueActionsAddress, + abi.encodeCall( + MorphoBlueActions.supplyCollateralAndBorrow, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), + 1e18, + 0.2e18, + address(0xa11ce), + address(0xa11ce) + ) + ), + 1e6 + ), + "calldata is Paycall.run(MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, cbETH, WETH), 1e18, 0.2e18, address(0xa11ce), address(0xa11ce));" + ); + assertEq(result.quarkOperations[1].scriptSources.length, 2); + assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[1].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) + ); + assertEq( + result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // Check the actions + assertEq(result.actions.length, 2, "two actions"); + // first action + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'"); + assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); + assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.BridgeActionContext({ + amount: 1e6, + price: USDC_PRICE, + token: USDC_1, + assetSymbol: "USDC", + chainId: 1, + recipient: address(0xa11ce), + destinationChainId: 8453, + bridgeType: Actions.BRIDGE_TYPE_CCTP + }) + ), + "action context encoded from BridgeActionContext" + ); + // second action + assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); + assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); + assertEq(result.actions[1].paymentMaxCost, 1e6, "payment should have max cost of 1e6"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 1e18; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = CBETH_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = cbEth_(8453); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "cbETH"; + + assertEq( + result.actions[1].actionContext, + abi.encode( + Actions.BorrowActionContext({ + amount: 0.2e18, + assetSymbol: "WETH", + chainId: 8453, + collateralAmounts: collateralAmounts, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + collateralAssetSymbols: collateralAssetSymbols, + comet: address(0), + price: WETH_PRICE, + token: weth_(8453), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "cbETH", "WETH")) + }) + ), + "action context encoded from BorrowActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } +} diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol new file mode 100644 index 00000000..7fdbdf48 --- /dev/null +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.23; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {Arrays} from "test/builder/lib/Arrays.sol"; +import {QuarkBuilderTest, Accounts, PaymentInfo, QuarkBuilder} from "test/builder/lib/QuarkBuilderTest.sol"; + +import {Actions} from "src/builder/Actions.sol"; +import {CCTPBridgeActions} from "src/BridgeScripts.sol"; +import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; +import {MorphoBlueActions} from "src/DeFiScripts.sol"; +import {Paycall} from "src/Paycall.sol"; +import {Strings} from "src/builder/Strings.sol"; +import {Multicall} from "src/Multicall.sol"; +import {WrapperActions} from "src/WrapperScripts.sol"; + +contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest {} diff --git a/test/builder/lib/QuarkBuilderTest.sol b/test/builder/lib/QuarkBuilderTest.sol index 19b33232..74a92f73 100644 --- a/test/builder/lib/QuarkBuilderTest.sol +++ b/test/builder/lib/QuarkBuilderTest.sol @@ -42,6 +42,16 @@ contract QuarkBuilderTest { address constant WETH_8453 = 0x4200000000000000000000000000000000000006; uint256 constant WETH_PRICE = 3000e8; + address constant WBTC_1 = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; + address constant WBTC_7777 = address(0xDEADBEEF); + address constant WBTC_8453 = address(0xDEADBEEF); + uint256 constant WBTC_PRICE = 66000e8; + + address constant CBETH_1 = 0xBe9895146f7AF43049ca1c1AE358B0541Ea49704; + address constant CBETH_7777 = address(0xDEADBEEF); + address constant CBETH_8453 = 0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22; + uint256 constant CBETH_PRICE = 3300e8; + address constant ETH_USD_PRICE_FEED_1 = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; address constant ETH_USD_PRICE_FEED_8453 = 0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70; @@ -173,6 +183,20 @@ contract QuarkBuilderTest { return accountBalances; } + function wbtc_(uint256 chainId) internal pure returns (address) { + if (chainId == 1) return WBTC_1; + if (chainId == 7777) return WBTC_7777; + if (chainId == 8453) return WBTC_8453; + revert("no mock WBTC for chain id"); + } + + function cbEth_(uint256 chainId) internal pure returns (address) { + if (chainId == 1) return CBETH_1; + if (chainId == 7777) return CBETH_7777; + if (chainId == 8453) return CBETH_8453; + revert("no mock cbETH for chain id"); + } + function link_(uint256 chainId) internal pure returns (address) { if (chainId == 1) return LINK_1; if (chainId == 7777) return LINK_7777; // Mock with random chain's LINK @@ -445,8 +469,12 @@ contract QuarkBuilderTest { return (eth_(), 18, WETH_PRICE); } else if (Strings.stringEq(assetSymbol, "LINK")) { return (link_(chainId), 18, LINK_PRICE); + } else if (Strings.stringEq(assetSymbol, "WBTC")) { + return (wbtc_(chainId), 8, WBTC_PRICE); + } else if (Strings.stringEq(assetSymbol, "cbETH")) { + return (cbEth_(chainId), 18, CBETH_PRICE); } else { - revert("unknown assetSymbol"); + revert("[Testlib QuarkBuilderTest]: unknown assetSymbol"); } } } diff --git a/test/on_chain_info_verificaion/MorphoInfo.t.sol b/test/on_chain_info_verificaion/MorphoInfo.t.sol index 4651ff7b..b10d52ce 100644 --- a/test/on_chain_info_verificaion/MorphoInfo.t.sol +++ b/test/on_chain_info_verificaion/MorphoInfo.t.sol @@ -81,7 +81,7 @@ contract MorphoInfoTest is Test { if (marketKeys[i].chainId == chainId) { MarketParams memory marketParams = MorphoInfo.getMarketParams(markets, marketKeys[i]); (uint128 totalSupplyAssets,,,, uint128 lastUpdate,) = - IMorpho(MorphoInfo.getMorphoAddress()).market(marketId(marketParams)); + IMorpho(MorphoInfo.getMorphoAddress()).market(MorphoInfo.marketId(marketParams)); assertGt( totalSupplyAssets, 0, @@ -112,10 +112,4 @@ contract MorphoInfoTest is Test { } } } - - // Helper function to convert MarketParams to bytes32 Id - // Reference: https://github.com/morpho-org/morpho-blue/blob/731e3f7ed97cf15f8fe00b86e4be5365eb3802ac/src/libraries/MarketParamsLib.sol - function marketId(MarketParams memory params) public pure returns (bytes32) { - return keccak256(abi.encode(params)); - } } From 4ef02bb692fcb3875a031dcbe27dcf56d53411a4 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 26 Aug 2024 20:50:15 -0700 Subject: [PATCH 22/50] weird stock too deep error, probably need to split QuarkBuilder.sol into multiple scripts --- src/builder/QuarkBuilder.sol | 18 +- test/builder/QuarkBuilderMorphoRepay.t.sol | 1063 +++++++++++++++++++- 2 files changed, 1065 insertions(+), 16 deletions(-) diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index 63e5304f..bb20eab6 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1187,25 +1187,17 @@ contract QuarkBuilder { bool isMaxRepay = repayIntent.amount == type(uint256).max; bool useQuotecall = false; // never use Quotecall - // Note: Need to get the token address of the repay and collateral tokens to find the right morpho market position info (i.e. total borrowed amount and shares) - Accounts.AssetPositions memory loanTokenAssetPosition = - Accounts.findAssetPositions(repayIntent.assetSymbol, repayIntent.chainId, chainAccountsList); - Accounts.AssetPositions memory collateralTokenAssetPosition = - Accounts.findAssetPositions(repayIntent.collateralAssetSymbol, repayIntent.chainId, chainAccountsList); - - uint256 repayAmount; - uint256 repayShares; + uint256 repayAmount = repayIntent.amount; + uint256 repayShares = 0; // Default to be 0 and not used, unless mas repay case if (isMaxRepay) { (repayAmount, repayShares) = morphorRepayMaxAmount( chainAccountsList, repayIntent.chainId, - loanTokenAssetPosition.asset, - collateralTokenAssetPosition.asset, + Accounts.findAssetPositions(repayIntent.assetSymbol, repayIntent.chainId, chainAccountsList).asset, + Accounts.findAssetPositions(repayIntent.collateralAssetSymbol, repayIntent.chainId, chainAccountsList) + .asset, repayIntent.repayer ); - } else { - repayAmount = repayIntent.amount; - repayShares = 0; } assertFundsAvailable(repayIntent.chainId, repayIntent.assetSymbol, repayAmount, chainAccountsList, payment); diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 7fdbdf48..4b999db0 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -5,8 +5,7 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import {Arrays} from "test/builder/lib/Arrays.sol"; -import {QuarkBuilderTest, Accounts, PaymentInfo, QuarkBuilder} from "test/builder/lib/QuarkBuilderTest.sol"; - +import {QuarkBuilderTest, Accounts, PaymentInfo} from "test/builder/lib/QuarkBuilderTest.sol"; import {Actions} from "src/builder/Actions.sol"; import {CCTPBridgeActions} from "src/BridgeScripts.sol"; import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; @@ -15,5 +14,1063 @@ import {Paycall} from "src/Paycall.sol"; import {Strings} from "src/builder/Strings.sol"; import {Multicall} from "src/Multicall.sol"; import {WrapperActions} from "src/WrapperScripts.sol"; +import {MorphoInfo} from "src/builder/MorphoInfo.sol"; +import {QuarkBuilder} from "src/builder/QuarkBuilder.sol"; + +contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { + function repayIntent_( + uint256 chainId, + string memory assetSymbol, + uint256 amount, + string memory collateralAssetSymbol, + uint256 collateralAmount + ) internal pure returns (QuarkBuilder.MorphoRepayIntent memory) { + return QuarkBuilder.MorphoRepayIntent({ + amount: amount, + assetSymbol: assetSymbol, + blockTimestamp: BLOCK_TIMESTAMP, + repayer: address(0xa11ce), + chainId: chainId, + collateralAmount: collateralAmount, + collateralAssetSymbol: collateralAssetSymbol + }); + } + + function testMorphoRepayFundsUnavailable() public { + QuarkBuilder builder = new QuarkBuilder(); + + vm.expectRevert(abi.encodeWithSelector(QuarkBuilder.FundsUnavailable.selector, "USDC", 1e6, 0)); + + builder.morphoRepay( + repayIntent_(1, "USDC", 1e6, "WBTC", 1e8), + chainAccountsList_(0e6), // but user has 0 USDC + paymentUsd_() + ); + } + + function testMorphoRepayMaxCostTooHigh() public { + QuarkBuilder builder = new QuarkBuilder(); + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 0.5e6}); // action costs .5 USDC + + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](1); + chainPortfolios[0] = ChainPortfolio({ + chainId: 8453, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + assetBalances: Arrays.uintArray(0.4e6, 0, 0, 1e18), // user does not have enough USDC + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + vm.expectRevert(QuarkBuilder.MaxCostTooHigh.selector); + + builder.morphoRepay( + repayIntent_(8453, "WETH", 1e18, "cbETH", 1e18), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) + ); + } + + // function testCometRepay() public { + // uint256[] memory collateralAmounts = new uint256[](1); + // collateralAmounts[0] = 1e18; + + // string[] memory collateralAssetSymbols = new string[](1); + // collateralAssetSymbols[0] = "LINK"; + + // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + // chainPortfolios[0] = ChainPortfolio({ + // chainId: 1, + // account: address(0xa11ce), + // nextNonce: 12, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + // chainPortfolios[1] = ChainPortfolio({ + // chainId: 8453, + // account: address(0xb0b), + // nextNonce: 2, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(0, 0, 0, 0), + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + + // QuarkBuilder builder = new QuarkBuilder(); + // QuarkBuilder.BuilderResult memory result = builder.cometRepay( + // repayIntent_( + // 1, + // cometUsdc_(1), + // "USDC", + // 1e6, // repaying 1 USDC + // collateralAssetSymbols, + // collateralAmounts // withdrawing 1 LINK + // ), + // chainAccountsFromChainPortfolios(chainPortfolios), + // paymentUsd_() + // ); + + // assertEq(result.paymentCurrency, "usd", "usd currency"); + + // // Check the quark operations + // assertEq(result.quarkOperations.length, 1, "one operation"); + // assertEq( + // result.quarkOperations[0].scriptAddress, + // address( + // uint160( + // uint256( + // keccak256( + // abi.encodePacked( + // bytes1(0xff), + // /* codeJar address */ + // address(CodeJarHelper.CODE_JAR_ADDRESS), + // uint256(0), + // /* script bytecode */ + // keccak256(type(CometRepayAndWithdrawMultipleAssets).creationCode) + // ) + // ) + // ) + // ) + // ), + // "script address is correct given the code jar address on mainnet" + // ); + // address[] memory collateralTokens = new address[](1); + // collateralTokens[0] = link_(1); + // assertEq( + // result.quarkOperations[0].scriptCalldata, + // abi.encodeCall( + // CometRepayAndWithdrawMultipleAssets.run, + // (cometUsdc_(1), collateralTokens, collateralAmounts, usdc_(1), 1e6) + // ), + // "calldata is CometRepayAndWithdrawMultipleAssets.run(COMET_1_USDC, [LINK], [1e18], USDC, 1e6);" + // ); + // assertEq(result.quarkOperations[0].scriptSources.length, 1); + // assertEq(result.quarkOperations[0].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); + // assertEq( + // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // check the actions + // assertEq(result.actions.length, 1, "one action"); + // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + // assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); + // assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); + // assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + + // uint256[] memory collateralTokenPrices = new uint256[](1); + // collateralTokenPrices[0] = LINK_PRICE; + + // assertEq( + // result.actions[0].actionContext, + // abi.encode( + // Actions.RepayActionContext({ + // amount: 1e6, + // assetSymbol: "USDC", + // chainId: 1, + // collateralAmounts: collateralAmounts, + // collateralAssetSymbols: collateralAssetSymbols, + // collateralTokenPrices: collateralTokenPrices, + // collateralTokens: collateralTokens, + // comet: cometUsdc_(1), + // price: USDC_PRICE, + // token: usdc_(1), + // morpho: address(0), + // morphoMarketId: bytes32(0) + // }) + // ), + // "action context encoded from RepayActionContext" + // ); + + // // TODO: Check the contents of the EIP712 data + // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + // } + + // function testCometRepayWithAutoWrapper() public { + // uint256[] memory collateralAmounts = new uint256[](1); + // collateralAmounts[0] = 1e18; + + // string[] memory collateralAssetSymbols = new string[](1); + // collateralAssetSymbols[0] = "LINK"; + + // address[] memory collateralTokens = new address[](1); + // collateralTokens[0] = link_(1); + + // uint256[] memory collateralTokenPrices = new uint256[](1); + // collateralTokenPrices[0] = LINK_PRICE; + + // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + // chainPortfolios[0] = ChainPortfolio({ + // chainId: 1, + // account: address(0xa11ce), + // nextNonce: 12, + // assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(0, 1e18, 0, 0), // has 1 ETH + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + // chainPortfolios[1] = ChainPortfolio({ + // chainId: 8453, + // account: address(0xb0b), + // nextNonce: 2, + // assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(0, 0, 0, 0), + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + + // QuarkBuilder builder = new QuarkBuilder(); + // QuarkBuilder.BuilderResult memory result = builder.cometRepay( + // repayIntent_( + // 1, + // cometWeth_(1), + // "WETH", + // 1e18, // repaying 1 WETH + // collateralAssetSymbols, + // collateralAmounts // withdrawing 1 LINK + // ), + // chainAccountsFromChainPortfolios(chainPortfolios), + // paymentUsd_() + // ); + + // assertEq(result.paymentCurrency, "usd", "usd currency"); + + // address multicallAddress = CodeJarHelper.getCodeAddress(type(Multicall).creationCode); + // address wrapperActionsAddress = CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode); + // address cometRepayAndWithdrawMultipleAssetsAddress = + // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); + // // Check the quark operations + // assertEq(result.quarkOperations.length, 1, "one merged operation"); + // assertEq( + // result.quarkOperations[0].scriptAddress, + // multicallAddress, + // "script address is correct given the code jar address on mainnet" + // ); + // address[] memory callContracts = new address[](2); + // callContracts[0] = wrapperActionsAddress; + // callContracts[1] = cometRepayAndWithdrawMultipleAssetsAddress; + // bytes[] memory callDatas = new bytes[](2); + // callDatas[0] = + // abi.encodeWithSelector(WrapperActions.wrapETH.selector, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18); + // callDatas[1] = abi.encodeCall( + // CometRepayAndWithdrawMultipleAssets.run, + // (cometWeth_(1), collateralTokens, collateralAmounts, weth_(1), 1e18) + // ); + + // assertEq( + // result.quarkOperations[0].scriptCalldata, + // abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), + // "calldata is Multicall.run([wrapperActionsAddress, cometRepayAndWithdrawMultipleAssetsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), CometRepayAndWithdrawMultipleAssets.run(COMET_1_WETH, collateralTokens, collateralAmounts, weth_(1), 1e18)" + // ); + // assertEq(result.quarkOperations[0].scriptSources.length, 3); + // assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); + // assertEq(result.quarkOperations[0].scriptSources[1], type(CometRepayAndWithdrawMultipleAssets).creationCode); + // assertEq(result.quarkOperations[0].scriptSources[2], type(Multicall).creationCode); + // assertEq( + // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // check the actions + // assertEq(result.actions.length, 1, "one action"); + // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + // assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); + // assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); + // assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + // assertEq( + // result.actions[0].actionContext, + // abi.encode( + // Actions.RepayActionContext({ + // amount: 1e18, + // assetSymbol: "WETH", + // chainId: 1, + // collateralAmounts: collateralAmounts, + // collateralAssetSymbols: collateralAssetSymbols, + // collateralTokenPrices: collateralTokenPrices, + // collateralTokens: collateralTokens, + // comet: cometWeth_(1), + // price: WETH_PRICE, + // token: weth_(1), + // morpho: address(0), + // morphoMarketId: bytes32(0) + // }) + // ), + // "action context encoded from RepayActionContext" + // ); + + // // TODO: Check the contents of the EIP712 data + // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + // } + + // function testCometRepayWithPaycall() public { + // string[] memory collateralAssetSymbols = new string[](1); + // collateralAssetSymbols[0] = "LINK"; + + // uint256[] memory collateralAmounts = new uint256[](1); + // collateralAmounts[0] = 1e18; + + // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + // chainPortfolios[0] = ChainPortfolio({ + // chainId: 1, + // account: address(0xa11ce), + // nextNonce: 12, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(2e6, 0, 0, 0), + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + // chainPortfolios[1] = ChainPortfolio({ + // chainId: 8453, + // account: address(0xb0b), + // nextNonce: 2, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(0, 0, 0, 0), + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + + // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + + // QuarkBuilder builder = new QuarkBuilder(); + // QuarkBuilder.BuilderResult memory result = builder.cometRepay( + // repayIntent_( + // 1, + // cometUsdc_(1), + // "USDC", + // 1e6, // repaying 1 USDC + // collateralAssetSymbols, + // collateralAmounts // withdrawing 1 LINK + // ), + // chainAccountsFromChainPortfolios(chainPortfolios), + // paymentUsdc_(maxCosts) // and paying with USDC + // ); + + // address cometRepayAndWithdrawMultipleAssetsAddress = + // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); + // address paycallAddress = paycallUsdc_(1); + + // assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // // Check the quark operations + // assertEq(result.quarkOperations.length, 1, "one operation"); + // assertEq( + // result.quarkOperations[0].scriptAddress, + // paycallAddress, + // "script address is correct given the code jar address on mainnet" + // ); + + // address[] memory collateralTokens = new address[](1); + // collateralTokens[0] = link_(1); + + // assertEq( + // result.quarkOperations[0].scriptCalldata, + // abi.encodeWithSelector( + // Paycall.run.selector, + // cometRepayAndWithdrawMultipleAssetsAddress, + // abi.encodeWithSelector( + // CometRepayAndWithdrawMultipleAssets.run.selector, + // cometUsdc_(1), + // collateralTokens, + // collateralAmounts, + // usdc_(1), + // 1e6 + // ), + // 0.1e6 + // ), + // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometUsdc_(1), [LINK_1], [1e18], USDC_1, 1e6), 0.1e6);" + // ); + // assertEq(result.quarkOperations[0].scriptSources.length, 2); + // assertEq(result.quarkOperations[0].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); + // assertEq( + // result.quarkOperations[0].scriptSources[1], + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + // ); + // assertEq( + // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // check the actions + // assertEq(result.actions.length, 1, "one action"); + // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); + // assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + + // uint256[] memory collateralTokenPrices = new uint256[](1); + // collateralTokenPrices[0] = LINK_PRICE; + + // assertEq( + // result.actions[0].actionContext, + // abi.encode( + // Actions.RepayActionContext({ + // amount: 1e6, + // assetSymbol: "USDC", + // chainId: 1, + // collateralAmounts: collateralAmounts, + // collateralAssetSymbols: collateralAssetSymbols, + // collateralTokenPrices: collateralTokenPrices, + // collateralTokens: collateralTokens, + // comet: cometUsdc_(1), + // price: USDC_PRICE, + // token: usdc_(1), + // morpho: address(0), + // morphoMarketId: bytes32(0) + // }) + // ), + // "action context encoded from RepayActionContext" + // ); + + // // TODO: Check the contents of the EIP712 data + // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + // } + + // // pay for a transaction with funds currently supplied as collateral + // function testCometRepayPayFromWithdraw() public { + // QuarkBuilder builder = new QuarkBuilder(); + // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.5e6}); // action costs .5 USDC + + // uint256[] memory collateralAmounts = new uint256[](1); + // collateralAmounts[0] = 1e6; + + // string[] memory collateralAssetSymbols = new string[](1); + // collateralAssetSymbols[0] = "USDC"; + + // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + // chainPortfolios[0] = ChainPortfolio({ + // chainId: 1, + // account: address(0xa11ce), + // nextNonce: 12, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(0, 0, 0, 1e18), + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + // chainPortfolios[1] = ChainPortfolio({ + // chainId: 8453, + // account: address(0xb0b), + // nextNonce: 2, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(0, 0, 0, 0), + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + + // QuarkBuilder.BuilderResult memory result = builder.cometRepay( + // repayIntent_( + // 1, + // cometWeth_(1), + // "WETH", + // 1e18, // repaying 1 WETH + // collateralAssetSymbols, + // collateralAmounts // and withdrawing 1 USDC + // ), + // chainAccountsFromChainPortfolios(chainPortfolios), + // paymentUsdc_(maxCosts) // user is paying with USDC that is currently supplied as collateral + // ); + + // address cometRepayAndWithdrawMultipleAssetsAddress = + // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); + // address paycallAddress = paycallUsdc_(1); + + // assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // address[] memory collateralTokens = new address[](1); + // collateralTokens[0] = usdc_(1); + + // // Check the quark operations + // assertEq(result.quarkOperations.length, 1, "one operation"); + // assertEq( + // result.quarkOperations[0].scriptAddress, + // paycallAddress, + // "script address is correct given the code jar address on mainnet" + // ); + // assertEq( + // result.quarkOperations[0].scriptCalldata, + // abi.encodeWithSelector( + // Paycall.run.selector, + // cometRepayAndWithdrawMultipleAssetsAddress, + // abi.encodeWithSelector( + // CometRepayAndWithdrawMultipleAssets.run.selector, + // cometWeth_(1), + // collateralTokens, + // collateralAmounts, + // weth_(1), + // 1e18 + // ), + // 0.5e6 + // ), + // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometWeth_(1), [USDC_1], [1e6], WETH_1, 1e18), 0.5e6);" + // ); + + // assertEq(result.quarkOperations[0].scriptSources.length, 2); + // assertEq(result.quarkOperations[0].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); + // assertEq( + // result.quarkOperations[0].scriptSources[1], + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + // ); + // assertEq( + // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // check the actions + // assertEq(result.actions.length, 1, "one action"); + // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); + // assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment max is set to .5e6 in this test case"); + + // uint256[] memory collateralTokenPrices = new uint256[](1); + // collateralTokenPrices[0] = USDC_PRICE; + + // assertEq( + // result.actions[0].actionContext, + // abi.encode( + // Actions.RepayActionContext({ + // amount: 1e18, + // assetSymbol: "WETH", + // chainId: 1, + // collateralAmounts: collateralAmounts, + // collateralAssetSymbols: collateralAssetSymbols, + // collateralTokenPrices: collateralTokenPrices, + // collateralTokens: collateralTokens, + // comet: cometWeth_(1), + // price: WETH_PRICE, + // token: weth_(1), + // morpho: address(0), + // morphoMarketId: bytes32(0) + // }) + // ), + // "action context encoded from RepayActionContext" + // ); + + // // TODO: Check the contents of the EIP712 data + // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + // } + + // function testCometRepayWithBridge() public { + // QuarkBuilder builder = new QuarkBuilder(); + + // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](2); + // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + // maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 0.2e6}); + + // string[] memory collateralAssetSymbols = new string[](1); + // collateralAssetSymbols[0] = "LINK"; + + // uint256[] memory collateralAmounts = new uint256[](1); + // collateralAmounts[0] = 1e18; + + // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + // chainPortfolios[0] = ChainPortfolio({ + // chainId: 1, + // account: address(0xa11ce), + // nextNonce: 12, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + // chainPortfolios[1] = ChainPortfolio({ + // chainId: 8453, + // account: address(0xb0b), + // nextNonce: 2, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + + // QuarkBuilder.BuilderResult memory result = builder.cometRepay( + // repayIntent_( + // 8453, + // cometUsdc_(8453), + // "USDC", // repaying 2 USDC, bridged from mainnet to base + // 2e6, + // collateralAssetSymbols, + // collateralAmounts + // ), + // chainAccountsFromChainPortfolios(chainPortfolios), + // paymentUsdc_(maxCosts) + // ); + + // address paycallAddress = paycallUsdc_(1); + // address paycallAddressBase = paycallUsdc_(8453); + // address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); + // address cometRepayAndWithdrawMultipleAssetsAddress = + // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); + + // assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // // Check the quark operations + // // first operation + // assertEq(result.quarkOperations.length, 2, "two operations"); + // assertEq( + // result.quarkOperations[0].scriptAddress, + // paycallAddress, + // "script address is correct given the code jar address on base" + // ); + // assertEq( + // result.quarkOperations[0].scriptCalldata, + // abi.encodeWithSelector( + // Paycall.run.selector, + // cctpBridgeActionsAddress, + // abi.encodeWithSelector( + // CCTPBridgeActions.bridgeUSDC.selector, + // address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), + // 2.2e6, // 2e6 repaid + 0.2e6 max cost on Base + // 6, + // bytes32(uint256(uint160(0xa11ce))), + // usdc_(1) + // ), + // 0.1e6 + // ), + // "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 2.2e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + // ); + // assertEq(result.quarkOperations[0].scriptSources.length, 2); + // assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); + // assertEq( + // result.quarkOperations[0].scriptSources[1], + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + // ); + // assertEq( + // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // second operation + // assertEq( + // result.quarkOperations[1].scriptAddress, + // paycallAddressBase, + // "script address[1] has been wrapped with paycall address" + // ); + + // address[] memory collateralTokens = new address[](1); + // collateralTokens[0] = link_(8453); + + // assertEq( + // result.quarkOperations[1].scriptCalldata, + // abi.encodeWithSelector( + // Paycall.run.selector, + // cometRepayAndWithdrawMultipleAssetsAddress, + // abi.encodeWithSelector( + // CometRepayAndWithdrawMultipleAssets.run.selector, + // cometUsdc_(8453), + // collateralTokens, + // collateralAmounts, + // usdc_(8453), + // 2e6 + // ), + // 0.2e6 + // ), + // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometUsdc_(8453), [LINK_8453], [1e18], USDC_8453, 2e6), 0.2e6);" + // ); + // assertEq(result.quarkOperations[1].scriptSources.length, 2); + // assertEq(result.quarkOperations[1].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); + // assertEq( + // result.quarkOperations[1].scriptSources[1], + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) + // ); + // assertEq( + // result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // Check the actions + // assertEq(result.actions.length, 2, "two actions"); + // // first action + // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'"); + // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); + // assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + // assertEq( + // result.actions[0].actionContext, + // abi.encode( + // Actions.BridgeActionContext({ + // amount: 2.2e6, + // assetSymbol: "USDC", + // bridgeType: Actions.BRIDGE_TYPE_CCTP, + // chainId: 1, + // destinationChainId: 8453, + // price: USDC_PRICE, + // recipient: address(0xa11ce), + // token: usdc_(1) + // }) + // ), + // "action context encoded from BridgeActionContext" + // ); + // // second action + // assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); + // assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[1].actionType, "REPAY", "action type is 'REPAY'"); + // assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + // assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); + // assertEq(result.actions[1].paymentMaxCost, 0.2e6, "payment should have max cost of 0.2e6"); + + // uint256[] memory collateralTokenPrices = new uint256[](1); + // collateralTokenPrices[0] = LINK_PRICE; + + // assertEq( + // result.actions[1].actionContext, + // abi.encode( + // Actions.RepayActionContext({ + // amount: 2e6, + // assetSymbol: "USDC", + // chainId: 8453, + // collateralAmounts: collateralAmounts, + // collateralAssetSymbols: collateralAssetSymbols, + // collateralTokenPrices: collateralTokenPrices, + // collateralTokens: collateralTokens, + // comet: cometUsdc_(8453), + // price: USDC_PRICE, + // token: usdc_(8453), + // morpho: address(0), + // morphoMarketId: bytes32(0) + // }) + // ), + // "action context encoded from RepayActionContext" + // ); + + // // TODO: Check the contents of the EIP712 data + // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + // } + + // function testCometRepayMax() public { + // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + + // address[] memory collateralTokens = new address[](0); + // uint256[] memory collateralAmounts = new uint256[](0); + // string[] memory collateralAssetSymbols = new string[](0); + + // CometPortfolio[] memory cometPortfolios = new CometPortfolio[](1); + // cometPortfolios[0] = CometPortfolio({ + // comet: cometUsdc_(1), + // baseSupplied: 0, + // baseBorrowed: 10e6, // currently borrowing 10 USDC + // collateralAssetSymbols: Arrays.stringArray("LINK"), + // collateralAssetBalances: Arrays.uintArray(0) + // }); + + // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](1); + // chainPortfolios[0] = ChainPortfolio({ + // chainId: 1, + // account: address(0xa11ce), + // nextNonce: 12, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC + // cometPortfolios: cometPortfolios, + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + + // QuarkBuilder builder = new QuarkBuilder(); + // QuarkBuilder.BuilderResult memory result = builder.cometRepay( + // repayIntent_( + // 1, + // cometUsdc_(1), + // "USDC", + // type(uint256).max, // repaying max (all 10 USDC) + // collateralAssetSymbols, + // collateralAmounts + // ), + // chainAccountsFromChainPortfolios(chainPortfolios), + // paymentUsdc_(maxCosts) + // ); + + // assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // address paycallAddress = CodeJarHelper.getCodeAddress( + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + // ); + // address cometRepayAndWithdrawMultipleAssetsAddress = + // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); + + // // Check the quark operations + // assertEq(result.quarkOperations.length, 1, "one operation"); + // assertEq( + // result.quarkOperations[0].scriptAddress, + // paycallAddress, + // "script address is correct given the code jar address on mainnet" + // ); + + // assertEq( + // result.quarkOperations[0].scriptCalldata, + // abi.encodeWithSelector( + // Paycall.run.selector, + // cometRepayAndWithdrawMultipleAssetsAddress, + // abi.encodeWithSelector( + // CometRepayAndWithdrawMultipleAssets.run.selector, + // cometUsdc_(1), + // collateralTokens, + // collateralAmounts, + // usdc_(1), + // type(uint256).max + // ), + // 0.1e6 + // ), + // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometUsdc_(1), [], [], USDC_1, uint256.max), 0.1e6);" + // ); + + // assertEq(result.quarkOperations[0].scriptSources.length, 2); + // assertEq(result.quarkOperations[0].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); + // assertEq( + // result.quarkOperations[0].scriptSources[1], + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + // ); + // assertEq( + // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // check the actions + // assertEq(result.actions.length, 1, "one action"); + // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); + // assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + + // uint256[] memory collateralTokenPrices = new uint256[](0); + + // assertEq( + // result.actions[0].actionContext, + // abi.encode( + // Actions.RepayActionContext({ + // amount: type(uint256).max, + // assetSymbol: "USDC", + // chainId: 1, + // collateralAmounts: collateralAmounts, + // collateralAssetSymbols: collateralAssetSymbols, + // collateralTokenPrices: collateralTokenPrices, + // collateralTokens: collateralTokens, + // comet: cometUsdc_(1), + // price: USDC_PRICE, + // token: usdc_(1), + // morpho: address(0), + // morphoMarketId: bytes32(0) + // }) + // ), + // "action context encoded from RepayActionContext" + // ); + + // // TODO: Check the contents of the EIP712 data + // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + // } + + // function testCometRepayMaxWithBridge() public { + // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](2); + // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + // maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 0.1e6}); + + // address[] memory collateralTokens = new address[](0); + // uint256[] memory collateralAmounts = new uint256[](0); + // string[] memory collateralAssetSymbols = new string[](0); + + // CometPortfolio[] memory cometPortfolios = new CometPortfolio[](1); + // cometPortfolios[0] = CometPortfolio({ + // comet: cometUsdc_(8453), + // baseSupplied: 0, + // baseBorrowed: 10e6, // currently borrowing 10 USDC on Base Comet + // collateralAssetSymbols: Arrays.stringArray("LINK"), + // collateralAssetBalances: Arrays.uintArray(0) + // }); + + // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + // chainPortfolios[0] = ChainPortfolio({ + // chainId: 1, + // account: address(0xa11ce), + // nextNonce: 12, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC on mainnet + // cometPortfolios: emptyCometPortfolios_(), + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + // chainPortfolios[1] = ChainPortfolio({ + // chainId: 8453, + // account: address(0xa11ce), + // nextNonce: 12, + // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), + // assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base + // cometPortfolios: cometPortfolios, + // morphoBluePortfolios: emptyMorphoBluePortfolios_() + // }); + + // QuarkBuilder builder = new QuarkBuilder(); + // QuarkBuilder.BuilderResult memory result = builder.cometRepay( + // repayIntent_( + // 8453, + // cometUsdc_(8453), + // "USDC", + // type(uint256).max, // repaying max (all 10 USDC) + // collateralAssetSymbols, + // collateralAmounts + // ), + // chainAccountsFromChainPortfolios(chainPortfolios), + // paymentUsdc_(maxCosts) + // ); + + // assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); + // address cometRepayAndWithdrawMultipleAssetsAddress = + // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); + // address paycallAddress = CodeJarHelper.getCodeAddress( + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + // ); + // address paycallAddressBase = CodeJarHelper.getCodeAddress( + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) + // ); + + // // Check the quark operations + // // first operation + // assertEq(result.quarkOperations.length, 2, "two operations"); + // assertEq( + // result.quarkOperations[0].scriptAddress, + // paycallAddress, + // "script address is correct given the code jar address on base" + // ); + // assertEq( + // result.quarkOperations[0].scriptCalldata, + // abi.encodeWithSelector( + // Paycall.run.selector, + // cctpBridgeActionsAddress, + // abi.encodeWithSelector( + // CCTPBridgeActions.bridgeUSDC.selector, + // address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), + // 10.11e6, // 10e6 repaid + .1% buffer + 0.1e6 max cost on Base + // 6, + // bytes32(uint256(uint160(0xa11ce))), + // usdc_(1) + // ), + // 0.1e6 + // ), + // "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 10.11e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + // ); + // assertEq(result.quarkOperations[0].scriptSources.length, 2); + // assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); + // assertEq( + // result.quarkOperations[0].scriptSources[1], + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + // ); + // assertEq( + // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // second operation + // assertEq( + // result.quarkOperations[1].scriptAddress, + // paycallAddressBase, + // "script address[1] has been wrapped with paycall address" + // ); + // assertEq( + // result.quarkOperations[1].scriptCalldata, + // abi.encodeWithSelector( + // Paycall.run.selector, + // cometRepayAndWithdrawMultipleAssetsAddress, + // abi.encodeWithSelector( + // CometRepayAndWithdrawMultipleAssets.run.selector, + // cometUsdc_(8453), + // collateralTokens, + // collateralAmounts, + // usdc_(8453), + // type(uint256).max + // ), + // 0.1e6 + // ), + // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometUsdc_(8453), [], [], USDC_8453, uint256.max), 0.1e6);" + // ); + // assertEq(result.quarkOperations[1].scriptSources.length, 2); + // assertEq(result.quarkOperations[1].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); + // assertEq( + // result.quarkOperations[1].scriptSources[1], + // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) + // ); + // assertEq( + // result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + // ); + + // // check the actions + // // first action + // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'"); + // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); + // assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + // assertEq( + // result.actions[0].actionContext, + // abi.encode( + // Actions.BridgeActionContext({ + // amount: 10.11e6, + // assetSymbol: "USDC", + // bridgeType: Actions.BRIDGE_TYPE_CCTP, + // chainId: 1, + // destinationChainId: 8453, + // price: USDC_PRICE, + // recipient: address(0xa11ce), + // token: USDC_1 + // }) + // ), + // "action context encoded from BridgeActionContext" + // ); + + // // second action + // assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); + // assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + // assertEq(result.actions[1].actionType, "REPAY", "action type is 'REPAY'"); + // assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + // assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); + // assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + + // uint256[] memory collateralTokenPrices = new uint256[](0); + + // assertEq( + // result.actions[1].actionContext, + // abi.encode( + // Actions.RepayActionContext({ + // amount: type(uint256).max, + // assetSymbol: "USDC", + // chainId: 8453, + // collateralAmounts: collateralAmounts, + // collateralAssetSymbols: collateralAssetSymbols, + // collateralTokenPrices: collateralTokenPrices, + // collateralTokens: collateralTokens, + // comet: cometUsdc_(8453), + // price: USDC_PRICE, + // token: usdc_(8453), + // morpho: address(0), + // morphoMarketId: bytes32(0) + // }) + // ), + // "action context encoded from RepayActionContext" + // ); -contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest {} + // // TODO: Check the contents of the EIP712 data + // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + // } +} From 525b99ef9ee2f3096a6f33ea42750ae504f3e571 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 28 Aug 2024 15:42:28 -0700 Subject: [PATCH 23/50] add tests --- src/builder/QuarkBuilder.sol | 31 +- test/builder/QuarkBuilderMorphoRepay.t.sol | 1894 +++++++++----------- 2 files changed, 918 insertions(+), 1007 deletions(-) diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index bb20eab6..89cc777e 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -31,6 +31,7 @@ contract QuarkBuilder { error InvalidInput(); error MaxCostTooHigh(); error MissingWrapperCounterpart(); + error InvalidRepayActionContext(); /* ===== Input Types ===== */ @@ -1537,16 +1538,32 @@ contract QuarkBuilder { if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_BORROW)) { continue; } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_REPAY)) { - Actions.RepayActionContext memory cometRepayActionContext = + Actions.RepayActionContext memory repayActionContext = abi.decode(nonBridgeAction.actionContext, (Actions.RepayActionContext)); - if (Strings.stringEqIgnoreCase(cometRepayActionContext.assetSymbol, paymentTokenSymbol)) { - if (cometRepayActionContext.amount == type(uint256).max) { - uint256 repayAmount = cometRepayMaxAmount( - chainAccountsList, cometRepayActionContext.chainId, cometRepayActionContext.comet, account - ); + if (Strings.stringEqIgnoreCase(repayActionContext.assetSymbol, paymentTokenSymbol)) { + if (repayActionContext.amount == type(uint256).max) { + uint256 repayAmount; + if (repayActionContext.comet != address(0)) { + // Comet repay + repayAmount = cometRepayMaxAmount( + chainAccountsList, repayActionContext.chainId, repayActionContext.comet, account + ); + } else if (repayActionContext.morpho != address(0)) { + // Morpho repay + (repayAmount,) = morphorRepayMaxAmount( + chainAccountsList, + repayActionContext.chainId, + repayActionContext.token, + repayActionContext.collateralTokens[0], + account + ); + } else { + revert InvalidRepayActionContext(); + } + paymentTokenCost += repayAmount; } else { - paymentTokenCost += cometRepayActionContext.amount; + paymentTokenCost += repayActionContext.amount; } } } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_SUPPLY)) { diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 4b999db0..bf97c4a3 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -16,6 +16,7 @@ import {Multicall} from "src/Multicall.sol"; import {WrapperActions} from "src/WrapperScripts.sol"; import {MorphoInfo} from "src/builder/MorphoInfo.sol"; import {QuarkBuilder} from "src/builder/QuarkBuilder.sol"; +import {TokenWrapper} from "src/builder/TokenWrapper.sol"; contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { function repayIntent_( @@ -73,1004 +74,897 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); } - // function testCometRepay() public { - // uint256[] memory collateralAmounts = new uint256[](1); - // collateralAmounts[0] = 1e18; - - // string[] memory collateralAssetSymbols = new string[](1); - // collateralAssetSymbols[0] = "LINK"; - - // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); - // chainPortfolios[0] = ChainPortfolio({ - // chainId: 1, - // account: address(0xa11ce), - // nextNonce: 12, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - // chainPortfolios[1] = ChainPortfolio({ - // chainId: 8453, - // account: address(0xb0b), - // nextNonce: 2, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(0, 0, 0, 0), - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - - // QuarkBuilder builder = new QuarkBuilder(); - // QuarkBuilder.BuilderResult memory result = builder.cometRepay( - // repayIntent_( - // 1, - // cometUsdc_(1), - // "USDC", - // 1e6, // repaying 1 USDC - // collateralAssetSymbols, - // collateralAmounts // withdrawing 1 LINK - // ), - // chainAccountsFromChainPortfolios(chainPortfolios), - // paymentUsd_() - // ); - - // assertEq(result.paymentCurrency, "usd", "usd currency"); - - // // Check the quark operations - // assertEq(result.quarkOperations.length, 1, "one operation"); - // assertEq( - // result.quarkOperations[0].scriptAddress, - // address( - // uint160( - // uint256( - // keccak256( - // abi.encodePacked( - // bytes1(0xff), - // /* codeJar address */ - // address(CodeJarHelper.CODE_JAR_ADDRESS), - // uint256(0), - // /* script bytecode */ - // keccak256(type(CometRepayAndWithdrawMultipleAssets).creationCode) - // ) - // ) - // ) - // ) - // ), - // "script address is correct given the code jar address on mainnet" - // ); - // address[] memory collateralTokens = new address[](1); - // collateralTokens[0] = link_(1); - // assertEq( - // result.quarkOperations[0].scriptCalldata, - // abi.encodeCall( - // CometRepayAndWithdrawMultipleAssets.run, - // (cometUsdc_(1), collateralTokens, collateralAmounts, usdc_(1), 1e6) - // ), - // "calldata is CometRepayAndWithdrawMultipleAssets.run(COMET_1_USDC, [LINK], [1e18], USDC, 1e6);" - // ); - // assertEq(result.quarkOperations[0].scriptSources.length, 1); - // assertEq(result.quarkOperations[0].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); - // assertEq( - // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // check the actions - // assertEq(result.actions.length, 1, "one action"); - // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); - // assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); - // assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); - // assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - - // uint256[] memory collateralTokenPrices = new uint256[](1); - // collateralTokenPrices[0] = LINK_PRICE; - - // assertEq( - // result.actions[0].actionContext, - // abi.encode( - // Actions.RepayActionContext({ - // amount: 1e6, - // assetSymbol: "USDC", - // chainId: 1, - // collateralAmounts: collateralAmounts, - // collateralAssetSymbols: collateralAssetSymbols, - // collateralTokenPrices: collateralTokenPrices, - // collateralTokens: collateralTokens, - // comet: cometUsdc_(1), - // price: USDC_PRICE, - // token: usdc_(1), - // morpho: address(0), - // morphoMarketId: bytes32(0) - // }) - // ), - // "action context encoded from RepayActionContext" - // ); - - // // TODO: Check the contents of the EIP712 data - // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); - // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); - // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); - // } - - // function testCometRepayWithAutoWrapper() public { - // uint256[] memory collateralAmounts = new uint256[](1); - // collateralAmounts[0] = 1e18; - - // string[] memory collateralAssetSymbols = new string[](1); - // collateralAssetSymbols[0] = "LINK"; - - // address[] memory collateralTokens = new address[](1); - // collateralTokens[0] = link_(1); - - // uint256[] memory collateralTokenPrices = new uint256[](1); - // collateralTokenPrices[0] = LINK_PRICE; - - // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); - // chainPortfolios[0] = ChainPortfolio({ - // chainId: 1, - // account: address(0xa11ce), - // nextNonce: 12, - // assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(0, 1e18, 0, 0), // has 1 ETH - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - // chainPortfolios[1] = ChainPortfolio({ - // chainId: 8453, - // account: address(0xb0b), - // nextNonce: 2, - // assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(0, 0, 0, 0), - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - - // QuarkBuilder builder = new QuarkBuilder(); - // QuarkBuilder.BuilderResult memory result = builder.cometRepay( - // repayIntent_( - // 1, - // cometWeth_(1), - // "WETH", - // 1e18, // repaying 1 WETH - // collateralAssetSymbols, - // collateralAmounts // withdrawing 1 LINK - // ), - // chainAccountsFromChainPortfolios(chainPortfolios), - // paymentUsd_() - // ); - - // assertEq(result.paymentCurrency, "usd", "usd currency"); - - // address multicallAddress = CodeJarHelper.getCodeAddress(type(Multicall).creationCode); - // address wrapperActionsAddress = CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode); - // address cometRepayAndWithdrawMultipleAssetsAddress = - // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); - // // Check the quark operations - // assertEq(result.quarkOperations.length, 1, "one merged operation"); - // assertEq( - // result.quarkOperations[0].scriptAddress, - // multicallAddress, - // "script address is correct given the code jar address on mainnet" - // ); - // address[] memory callContracts = new address[](2); - // callContracts[0] = wrapperActionsAddress; - // callContracts[1] = cometRepayAndWithdrawMultipleAssetsAddress; - // bytes[] memory callDatas = new bytes[](2); - // callDatas[0] = - // abi.encodeWithSelector(WrapperActions.wrapETH.selector, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18); - // callDatas[1] = abi.encodeCall( - // CometRepayAndWithdrawMultipleAssets.run, - // (cometWeth_(1), collateralTokens, collateralAmounts, weth_(1), 1e18) - // ); - - // assertEq( - // result.quarkOperations[0].scriptCalldata, - // abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), - // "calldata is Multicall.run([wrapperActionsAddress, cometRepayAndWithdrawMultipleAssetsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), CometRepayAndWithdrawMultipleAssets.run(COMET_1_WETH, collateralTokens, collateralAmounts, weth_(1), 1e18)" - // ); - // assertEq(result.quarkOperations[0].scriptSources.length, 3); - // assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); - // assertEq(result.quarkOperations[0].scriptSources[1], type(CometRepayAndWithdrawMultipleAssets).creationCode); - // assertEq(result.quarkOperations[0].scriptSources[2], type(Multicall).creationCode); - // assertEq( - // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // check the actions - // assertEq(result.actions.length, 1, "one action"); - // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); - // assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); - // assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); - // assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - // assertEq( - // result.actions[0].actionContext, - // abi.encode( - // Actions.RepayActionContext({ - // amount: 1e18, - // assetSymbol: "WETH", - // chainId: 1, - // collateralAmounts: collateralAmounts, - // collateralAssetSymbols: collateralAssetSymbols, - // collateralTokenPrices: collateralTokenPrices, - // collateralTokens: collateralTokens, - // comet: cometWeth_(1), - // price: WETH_PRICE, - // token: weth_(1), - // morpho: address(0), - // morphoMarketId: bytes32(0) - // }) - // ), - // "action context encoded from RepayActionContext" - // ); - - // // TODO: Check the contents of the EIP712 data - // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); - // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); - // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); - // } - - // function testCometRepayWithPaycall() public { - // string[] memory collateralAssetSymbols = new string[](1); - // collateralAssetSymbols[0] = "LINK"; - - // uint256[] memory collateralAmounts = new uint256[](1); - // collateralAmounts[0] = 1e18; - - // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); - // chainPortfolios[0] = ChainPortfolio({ - // chainId: 1, - // account: address(0xa11ce), - // nextNonce: 12, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(2e6, 0, 0, 0), - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - // chainPortfolios[1] = ChainPortfolio({ - // chainId: 8453, - // account: address(0xb0b), - // nextNonce: 2, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(0, 0, 0, 0), - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - - // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); - // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); - - // QuarkBuilder builder = new QuarkBuilder(); - // QuarkBuilder.BuilderResult memory result = builder.cometRepay( - // repayIntent_( - // 1, - // cometUsdc_(1), - // "USDC", - // 1e6, // repaying 1 USDC - // collateralAssetSymbols, - // collateralAmounts // withdrawing 1 LINK - // ), - // chainAccountsFromChainPortfolios(chainPortfolios), - // paymentUsdc_(maxCosts) // and paying with USDC - // ); - - // address cometRepayAndWithdrawMultipleAssetsAddress = - // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); - // address paycallAddress = paycallUsdc_(1); - - // assertEq(result.paymentCurrency, "usdc", "usdc currency"); - - // // Check the quark operations - // assertEq(result.quarkOperations.length, 1, "one operation"); - // assertEq( - // result.quarkOperations[0].scriptAddress, - // paycallAddress, - // "script address is correct given the code jar address on mainnet" - // ); - - // address[] memory collateralTokens = new address[](1); - // collateralTokens[0] = link_(1); - - // assertEq( - // result.quarkOperations[0].scriptCalldata, - // abi.encodeWithSelector( - // Paycall.run.selector, - // cometRepayAndWithdrawMultipleAssetsAddress, - // abi.encodeWithSelector( - // CometRepayAndWithdrawMultipleAssets.run.selector, - // cometUsdc_(1), - // collateralTokens, - // collateralAmounts, - // usdc_(1), - // 1e6 - // ), - // 0.1e6 - // ), - // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometUsdc_(1), [LINK_1], [1e18], USDC_1, 1e6), 0.1e6);" - // ); - // assertEq(result.quarkOperations[0].scriptSources.length, 2); - // assertEq(result.quarkOperations[0].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); - // assertEq( - // result.quarkOperations[0].scriptSources[1], - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) - // ); - // assertEq( - // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // check the actions - // assertEq(result.actions.length, 1, "one action"); - // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); - // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); - // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); - // assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); - - // uint256[] memory collateralTokenPrices = new uint256[](1); - // collateralTokenPrices[0] = LINK_PRICE; - - // assertEq( - // result.actions[0].actionContext, - // abi.encode( - // Actions.RepayActionContext({ - // amount: 1e6, - // assetSymbol: "USDC", - // chainId: 1, - // collateralAmounts: collateralAmounts, - // collateralAssetSymbols: collateralAssetSymbols, - // collateralTokenPrices: collateralTokenPrices, - // collateralTokens: collateralTokens, - // comet: cometUsdc_(1), - // price: USDC_PRICE, - // token: usdc_(1), - // morpho: address(0), - // morphoMarketId: bytes32(0) - // }) - // ), - // "action context encoded from RepayActionContext" - // ); - - // // TODO: Check the contents of the EIP712 data - // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); - // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); - // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); - // } - - // // pay for a transaction with funds currently supplied as collateral - // function testCometRepayPayFromWithdraw() public { - // QuarkBuilder builder = new QuarkBuilder(); - // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); - // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.5e6}); // action costs .5 USDC - - // uint256[] memory collateralAmounts = new uint256[](1); - // collateralAmounts[0] = 1e6; - - // string[] memory collateralAssetSymbols = new string[](1); - // collateralAssetSymbols[0] = "USDC"; - - // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); - // chainPortfolios[0] = ChainPortfolio({ - // chainId: 1, - // account: address(0xa11ce), - // nextNonce: 12, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(0, 0, 0, 1e18), - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - // chainPortfolios[1] = ChainPortfolio({ - // chainId: 8453, - // account: address(0xb0b), - // nextNonce: 2, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(0, 0, 0, 0), - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - - // QuarkBuilder.BuilderResult memory result = builder.cometRepay( - // repayIntent_( - // 1, - // cometWeth_(1), - // "WETH", - // 1e18, // repaying 1 WETH - // collateralAssetSymbols, - // collateralAmounts // and withdrawing 1 USDC - // ), - // chainAccountsFromChainPortfolios(chainPortfolios), - // paymentUsdc_(maxCosts) // user is paying with USDC that is currently supplied as collateral - // ); - - // address cometRepayAndWithdrawMultipleAssetsAddress = - // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); - // address paycallAddress = paycallUsdc_(1); - - // assertEq(result.paymentCurrency, "usdc", "usdc currency"); - - // address[] memory collateralTokens = new address[](1); - // collateralTokens[0] = usdc_(1); - - // // Check the quark operations - // assertEq(result.quarkOperations.length, 1, "one operation"); - // assertEq( - // result.quarkOperations[0].scriptAddress, - // paycallAddress, - // "script address is correct given the code jar address on mainnet" - // ); - // assertEq( - // result.quarkOperations[0].scriptCalldata, - // abi.encodeWithSelector( - // Paycall.run.selector, - // cometRepayAndWithdrawMultipleAssetsAddress, - // abi.encodeWithSelector( - // CometRepayAndWithdrawMultipleAssets.run.selector, - // cometWeth_(1), - // collateralTokens, - // collateralAmounts, - // weth_(1), - // 1e18 - // ), - // 0.5e6 - // ), - // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometWeth_(1), [USDC_1], [1e6], WETH_1, 1e18), 0.5e6);" - // ); - - // assertEq(result.quarkOperations[0].scriptSources.length, 2); - // assertEq(result.quarkOperations[0].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); - // assertEq( - // result.quarkOperations[0].scriptSources[1], - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) - // ); - // assertEq( - // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // check the actions - // assertEq(result.actions.length, 1, "one action"); - // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); - // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); - // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); - // assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment max is set to .5e6 in this test case"); - - // uint256[] memory collateralTokenPrices = new uint256[](1); - // collateralTokenPrices[0] = USDC_PRICE; - - // assertEq( - // result.actions[0].actionContext, - // abi.encode( - // Actions.RepayActionContext({ - // amount: 1e18, - // assetSymbol: "WETH", - // chainId: 1, - // collateralAmounts: collateralAmounts, - // collateralAssetSymbols: collateralAssetSymbols, - // collateralTokenPrices: collateralTokenPrices, - // collateralTokens: collateralTokens, - // comet: cometWeth_(1), - // price: WETH_PRICE, - // token: weth_(1), - // morpho: address(0), - // morphoMarketId: bytes32(0) - // }) - // ), - // "action context encoded from RepayActionContext" - // ); - - // // TODO: Check the contents of the EIP712 data - // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); - // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); - // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); - // } - - // function testCometRepayWithBridge() public { - // QuarkBuilder builder = new QuarkBuilder(); - - // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](2); - // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); - // maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 0.2e6}); - - // string[] memory collateralAssetSymbols = new string[](1); - // collateralAssetSymbols[0] = "LINK"; - - // uint256[] memory collateralAmounts = new uint256[](1); - // collateralAmounts[0] = 1e18; - - // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); - // chainPortfolios[0] = ChainPortfolio({ - // chainId: 1, - // account: address(0xa11ce), - // nextNonce: 12, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - // chainPortfolios[1] = ChainPortfolio({ - // chainId: 8453, - // account: address(0xb0b), - // nextNonce: 2, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - - // QuarkBuilder.BuilderResult memory result = builder.cometRepay( - // repayIntent_( - // 8453, - // cometUsdc_(8453), - // "USDC", // repaying 2 USDC, bridged from mainnet to base - // 2e6, - // collateralAssetSymbols, - // collateralAmounts - // ), - // chainAccountsFromChainPortfolios(chainPortfolios), - // paymentUsdc_(maxCosts) - // ); - - // address paycallAddress = paycallUsdc_(1); - // address paycallAddressBase = paycallUsdc_(8453); - // address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - // address cometRepayAndWithdrawMultipleAssetsAddress = - // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); - - // assertEq(result.paymentCurrency, "usdc", "usdc currency"); - - // // Check the quark operations - // // first operation - // assertEq(result.quarkOperations.length, 2, "two operations"); - // assertEq( - // result.quarkOperations[0].scriptAddress, - // paycallAddress, - // "script address is correct given the code jar address on base" - // ); - // assertEq( - // result.quarkOperations[0].scriptCalldata, - // abi.encodeWithSelector( - // Paycall.run.selector, - // cctpBridgeActionsAddress, - // abi.encodeWithSelector( - // CCTPBridgeActions.bridgeUSDC.selector, - // address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), - // 2.2e6, // 2e6 repaid + 0.2e6 max cost on Base - // 6, - // bytes32(uint256(uint160(0xa11ce))), - // usdc_(1) - // ), - // 0.1e6 - // ), - // "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 2.2e6, 6, 0xa11ce, USDC_1)), 0.1e6);" - // ); - // assertEq(result.quarkOperations[0].scriptSources.length, 2); - // assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); - // assertEq( - // result.quarkOperations[0].scriptSources[1], - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) - // ); - // assertEq( - // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // second operation - // assertEq( - // result.quarkOperations[1].scriptAddress, - // paycallAddressBase, - // "script address[1] has been wrapped with paycall address" - // ); - - // address[] memory collateralTokens = new address[](1); - // collateralTokens[0] = link_(8453); - - // assertEq( - // result.quarkOperations[1].scriptCalldata, - // abi.encodeWithSelector( - // Paycall.run.selector, - // cometRepayAndWithdrawMultipleAssetsAddress, - // abi.encodeWithSelector( - // CometRepayAndWithdrawMultipleAssets.run.selector, - // cometUsdc_(8453), - // collateralTokens, - // collateralAmounts, - // usdc_(8453), - // 2e6 - // ), - // 0.2e6 - // ), - // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometUsdc_(8453), [LINK_8453], [1e18], USDC_8453, 2e6), 0.2e6);" - // ); - // assertEq(result.quarkOperations[1].scriptSources.length, 2); - // assertEq(result.quarkOperations[1].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); - // assertEq( - // result.quarkOperations[1].scriptSources[1], - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) - // ); - // assertEq( - // result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // Check the actions - // assertEq(result.actions.length, 2, "two actions"); - // // first action - // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'"); - // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); - // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); - // assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); - // assertEq( - // result.actions[0].actionContext, - // abi.encode( - // Actions.BridgeActionContext({ - // amount: 2.2e6, - // assetSymbol: "USDC", - // bridgeType: Actions.BRIDGE_TYPE_CCTP, - // chainId: 1, - // destinationChainId: 8453, - // price: USDC_PRICE, - // recipient: address(0xa11ce), - // token: usdc_(1) - // }) - // ), - // "action context encoded from BridgeActionContext" - // ); - // // second action - // assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - // assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[1].actionType, "REPAY", "action type is 'REPAY'"); - // assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); - // assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); - // assertEq(result.actions[1].paymentMaxCost, 0.2e6, "payment should have max cost of 0.2e6"); - - // uint256[] memory collateralTokenPrices = new uint256[](1); - // collateralTokenPrices[0] = LINK_PRICE; - - // assertEq( - // result.actions[1].actionContext, - // abi.encode( - // Actions.RepayActionContext({ - // amount: 2e6, - // assetSymbol: "USDC", - // chainId: 8453, - // collateralAmounts: collateralAmounts, - // collateralAssetSymbols: collateralAssetSymbols, - // collateralTokenPrices: collateralTokenPrices, - // collateralTokens: collateralTokens, - // comet: cometUsdc_(8453), - // price: USDC_PRICE, - // token: usdc_(8453), - // morpho: address(0), - // morphoMarketId: bytes32(0) - // }) - // ), - // "action context encoded from RepayActionContext" - // ); - - // // TODO: Check the contents of the EIP712 data - // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); - // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); - // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); - // } - - // function testCometRepayMax() public { - // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); - // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); - - // address[] memory collateralTokens = new address[](0); - // uint256[] memory collateralAmounts = new uint256[](0); - // string[] memory collateralAssetSymbols = new string[](0); - - // CometPortfolio[] memory cometPortfolios = new CometPortfolio[](1); - // cometPortfolios[0] = CometPortfolio({ - // comet: cometUsdc_(1), - // baseSupplied: 0, - // baseBorrowed: 10e6, // currently borrowing 10 USDC - // collateralAssetSymbols: Arrays.stringArray("LINK"), - // collateralAssetBalances: Arrays.uintArray(0) - // }); - - // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](1); - // chainPortfolios[0] = ChainPortfolio({ - // chainId: 1, - // account: address(0xa11ce), - // nextNonce: 12, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC - // cometPortfolios: cometPortfolios, - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - - // QuarkBuilder builder = new QuarkBuilder(); - // QuarkBuilder.BuilderResult memory result = builder.cometRepay( - // repayIntent_( - // 1, - // cometUsdc_(1), - // "USDC", - // type(uint256).max, // repaying max (all 10 USDC) - // collateralAssetSymbols, - // collateralAmounts - // ), - // chainAccountsFromChainPortfolios(chainPortfolios), - // paymentUsdc_(maxCosts) - // ); - - // assertEq(result.paymentCurrency, "usdc", "usdc currency"); - - // address paycallAddress = CodeJarHelper.getCodeAddress( - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) - // ); - // address cometRepayAndWithdrawMultipleAssetsAddress = - // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); - - // // Check the quark operations - // assertEq(result.quarkOperations.length, 1, "one operation"); - // assertEq( - // result.quarkOperations[0].scriptAddress, - // paycallAddress, - // "script address is correct given the code jar address on mainnet" - // ); - - // assertEq( - // result.quarkOperations[0].scriptCalldata, - // abi.encodeWithSelector( - // Paycall.run.selector, - // cometRepayAndWithdrawMultipleAssetsAddress, - // abi.encodeWithSelector( - // CometRepayAndWithdrawMultipleAssets.run.selector, - // cometUsdc_(1), - // collateralTokens, - // collateralAmounts, - // usdc_(1), - // type(uint256).max - // ), - // 0.1e6 - // ), - // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometUsdc_(1), [], [], USDC_1, uint256.max), 0.1e6);" - // ); - - // assertEq(result.quarkOperations[0].scriptSources.length, 2); - // assertEq(result.quarkOperations[0].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); - // assertEq( - // result.quarkOperations[0].scriptSources[1], - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) - // ); - // assertEq( - // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // check the actions - // assertEq(result.actions.length, 1, "one action"); - // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); - // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); - // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); - // assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); - - // uint256[] memory collateralTokenPrices = new uint256[](0); - - // assertEq( - // result.actions[0].actionContext, - // abi.encode( - // Actions.RepayActionContext({ - // amount: type(uint256).max, - // assetSymbol: "USDC", - // chainId: 1, - // collateralAmounts: collateralAmounts, - // collateralAssetSymbols: collateralAssetSymbols, - // collateralTokenPrices: collateralTokenPrices, - // collateralTokens: collateralTokens, - // comet: cometUsdc_(1), - // price: USDC_PRICE, - // token: usdc_(1), - // morpho: address(0), - // morphoMarketId: bytes32(0) - // }) - // ), - // "action context encoded from RepayActionContext" - // ); - - // // TODO: Check the contents of the EIP712 data - // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); - // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); - // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); - // } - - // function testCometRepayMaxWithBridge() public { - // PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](2); - // maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); - // maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 0.1e6}); - - // address[] memory collateralTokens = new address[](0); - // uint256[] memory collateralAmounts = new uint256[](0); - // string[] memory collateralAssetSymbols = new string[](0); - - // CometPortfolio[] memory cometPortfolios = new CometPortfolio[](1); - // cometPortfolios[0] = CometPortfolio({ - // comet: cometUsdc_(8453), - // baseSupplied: 0, - // baseBorrowed: 10e6, // currently borrowing 10 USDC on Base Comet - // collateralAssetSymbols: Arrays.stringArray("LINK"), - // collateralAssetBalances: Arrays.uintArray(0) - // }); - - // ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); - // chainPortfolios[0] = ChainPortfolio({ - // chainId: 1, - // account: address(0xa11ce), - // nextNonce: 12, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC on mainnet - // cometPortfolios: emptyCometPortfolios_(), - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - // chainPortfolios[1] = ChainPortfolio({ - // chainId: 8453, - // account: address(0xa11ce), - // nextNonce: 12, - // assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), - // assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base - // cometPortfolios: cometPortfolios, - // morphoBluePortfolios: emptyMorphoBluePortfolios_() - // }); - - // QuarkBuilder builder = new QuarkBuilder(); - // QuarkBuilder.BuilderResult memory result = builder.cometRepay( - // repayIntent_( - // 8453, - // cometUsdc_(8453), - // "USDC", - // type(uint256).max, // repaying max (all 10 USDC) - // collateralAssetSymbols, - // collateralAmounts - // ), - // chainAccountsFromChainPortfolios(chainPortfolios), - // paymentUsdc_(maxCosts) - // ); - - // assertEq(result.paymentCurrency, "usdc", "usdc currency"); - - // address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - // address cometRepayAndWithdrawMultipleAssetsAddress = - // CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode); - // address paycallAddress = CodeJarHelper.getCodeAddress( - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) - // ); - // address paycallAddressBase = CodeJarHelper.getCodeAddress( - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) - // ); - - // // Check the quark operations - // // first operation - // assertEq(result.quarkOperations.length, 2, "two operations"); - // assertEq( - // result.quarkOperations[0].scriptAddress, - // paycallAddress, - // "script address is correct given the code jar address on base" - // ); - // assertEq( - // result.quarkOperations[0].scriptCalldata, - // abi.encodeWithSelector( - // Paycall.run.selector, - // cctpBridgeActionsAddress, - // abi.encodeWithSelector( - // CCTPBridgeActions.bridgeUSDC.selector, - // address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), - // 10.11e6, // 10e6 repaid + .1% buffer + 0.1e6 max cost on Base - // 6, - // bytes32(uint256(uint160(0xa11ce))), - // usdc_(1) - // ), - // 0.1e6 - // ), - // "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 10.11e6, 6, 0xa11ce, USDC_1)), 0.1e6);" - // ); - // assertEq(result.quarkOperations[0].scriptSources.length, 2); - // assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); - // assertEq( - // result.quarkOperations[0].scriptSources[1], - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) - // ); - // assertEq( - // result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // second operation - // assertEq( - // result.quarkOperations[1].scriptAddress, - // paycallAddressBase, - // "script address[1] has been wrapped with paycall address" - // ); - // assertEq( - // result.quarkOperations[1].scriptCalldata, - // abi.encodeWithSelector( - // Paycall.run.selector, - // cometRepayAndWithdrawMultipleAssetsAddress, - // abi.encodeWithSelector( - // CometRepayAndWithdrawMultipleAssets.run.selector, - // cometUsdc_(8453), - // collateralTokens, - // collateralAmounts, - // usdc_(8453), - // type(uint256).max - // ), - // 0.1e6 - // ), - // "calldata is Paycall.run(CometRepayAndWithdrawMultipleAssets.run(cometUsdc_(8453), [], [], USDC_8453, uint256.max), 0.1e6);" - // ); - // assertEq(result.quarkOperations[1].scriptSources.length, 2); - // assertEq(result.quarkOperations[1].scriptSources[0], type(CometRepayAndWithdrawMultipleAssets).creationCode); - // assertEq( - // result.quarkOperations[1].scriptSources[1], - // abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) - // ); - // assertEq( - // result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" - // ); - - // // check the actions - // // first action - // assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - // assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'"); - // assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); - // assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); - // assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); - // assertEq( - // result.actions[0].actionContext, - // abi.encode( - // Actions.BridgeActionContext({ - // amount: 10.11e6, - // assetSymbol: "USDC", - // bridgeType: Actions.BRIDGE_TYPE_CCTP, - // chainId: 1, - // destinationChainId: 8453, - // price: USDC_PRICE, - // recipient: address(0xa11ce), - // token: USDC_1 - // }) - // ), - // "action context encoded from BridgeActionContext" - // ); - - // // second action - // assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - // assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - // assertEq(result.actions[1].actionType, "REPAY", "action type is 'REPAY'"); - // assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); - // assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); - // assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); - - // uint256[] memory collateralTokenPrices = new uint256[](0); - - // assertEq( - // result.actions[1].actionContext, - // abi.encode( - // Actions.RepayActionContext({ - // amount: type(uint256).max, - // assetSymbol: "USDC", - // chainId: 8453, - // collateralAmounts: collateralAmounts, - // collateralAssetSymbols: collateralAssetSymbols, - // collateralTokenPrices: collateralTokenPrices, - // collateralTokens: collateralTokens, - // comet: cometUsdc_(8453), - // price: USDC_PRICE, - // token: usdc_(8453), - // morpho: address(0), - // morphoMarketId: bytes32(0) - // }) - // ), - // "action context encoded from RepayActionContext" - // ); - - // // TODO: Check the contents of the EIP712 data - // assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); - // assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); - // assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); - // } + function testMorphoRepay() public { + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoRepay( + repayIntent_( + 1, + "USDC", + 1e6, // repaying 1 USDC + "WBTC", + 0e8 + ), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsd_() + ); + + assertEq(result.paymentCurrency, "usd", "usd currency"); + + // Check the quark operations + assertEq(result.quarkOperations.length, 1, "one operation"); + assertEq( + result.quarkOperations[0].scriptAddress, + address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + /* codeJar address */ + address(CodeJarHelper.CODE_JAR_ADDRESS), + uint256(0), + /* script bytecode */ + keccak256(type(MorphoBlueActions).creationCode) + ) + ) + ) + ) + ), + "script address is correct given the code jar address on mainnet" + ); + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeCall( + MorphoBlueActions.repayAndWithdrawCollateral, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(1, "WBTC", "USDC"), + 1e6, + 0, + 0e8, + address(0xa11ce), + address(0xa11ce) + ) + ), + "calldata is MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 0e8, address(0xa11ce), address(0xa11ce));" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 1); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + assertEq(result.actions.length, 1, "one action"); + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); + assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); + assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 0e8; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WBTC_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = wbtc_(1); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WBTC"; + + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.RepayActionContext({ + amount: 1e6, + assetSymbol: "USDC", + chainId: 1, + collateralAmounts: collateralAmounts, + collateralAssetSymbols: collateralAssetSymbols, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + comet: address(0), + price: USDC_PRICE, + token: usdc_(1), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) + }) + ), + "action context encoded from RepayActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testCometRepayWithAutoWrapper() public { + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "ETH", "cbETH", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xa11ce), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "ETH", "cbETH", "WETH"), + assetBalances: Arrays.uintArray(0, 1e18, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoRepay( + repayIntent_( + 8453, + "WETH", + 1e18, // repaying 1 WETH + "cbETH", + 0e18 + ), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsd_() + ); + + assertEq(result.paymentCurrency, "usd", "usd currency"); + + address multicallAddress = CodeJarHelper.getCodeAddress(type(Multicall).creationCode); + address wrapperActionsAddress = CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode); + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + + // Check the quark operations + assertEq(result.quarkOperations.length, 1, "one merged operation"); + assertEq( + result.quarkOperations[0].scriptAddress, + multicallAddress, + "script address is correct given the code jar address on mainnet" + ); + address[] memory callContracts = new address[](2); + callContracts[0] = wrapperActionsAddress; + callContracts[1] = morphoBlueActionsAddress; + bytes[] memory callDatas = new bytes[](2); + callDatas[0] = abi.encodeWithSelector( + WrapperActions.wrapETH.selector, TokenWrapper.getKnownWrapperTokenPair(8453, "WETH").wrapper, 1e18 + ); + callDatas[1] = abi.encodeCall( + MorphoBlueActions.repayAndWithdrawCollateral, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), + 1e18, + 0, + 0e18, + address(0xa11ce), + address(0xa11ce) + ) + ); + + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), + "calldata is Multicall.run([wrapperActionsAddress, morphoBlueActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 0, 0e18, address(0xa11ce), address(0xa11ce))" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 3); + assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[1], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[2], type(Multicall).creationCode); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + assertEq(result.actions.length, 1, "one action"); + assertEq(result.actions[0].chainId, 8453, "operation is on chainid 8453"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); + assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); + assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 0e18; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = CBETH_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = cbEth_(8453); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "cbETH"; + + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.RepayActionContext({ + amount: 1e18, + assetSymbol: "WETH", + chainId: 8453, + collateralAmounts: collateralAmounts, + collateralAssetSymbols: collateralAssetSymbols, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + comet: address(0), + price: WETH_PRICE, + token: weth_(8453), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "cbETH", "WETH")) + }) + ), + "action context encoded from RepayActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testCometRepayWithPaycall() public { + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(2e6, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoRepay( + repayIntent_( + 1, + "USDC", + 1e6, // repaying 1 USDC + "WBTC", + 0e8 + ), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) // and paying with USDC + ); + + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address paycallAddress = paycallUsdc_(1); + + assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // Check the quark operations + assertEq(result.quarkOperations.length, 1, "one operation"); + assertEq( + result.quarkOperations[0].scriptAddress, + paycallAddress, + "script address is correct given the code jar address on mainnet" + ); + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + morphoBlueActionsAddress, + abi.encodeCall( + MorphoBlueActions.repayAndWithdrawCollateral, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(1, "WBTC", "USDC"), + 1e6, + 0, + 0e8, + address(0xa11ce), + address(0xa11ce) + ) + ), + 0.1e6 + ), + "calldata is Paycall.run(MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 0e8, address(0xa11ce), address(0xa11ce));" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 2); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[0].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + assertEq(result.actions.length, 1, "one action"); + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); + assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 0e18; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WBTC_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = wbtc_(1); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WBTC"; + + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.RepayActionContext({ + amount: 1e6, + assetSymbol: "USDC", + chainId: 1, + collateralAmounts: collateralAmounts, + collateralAssetSymbols: collateralAssetSymbols, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + comet: address(0), + price: USDC_PRICE, + token: usdc_(1), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) + }) + ), + "action context encoded from RepayActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testCometRepayWithBridge() public { + QuarkBuilder builder = new QuarkBuilder(); + + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](2); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 0.2e6}); + + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xb0b), + nextNonce: 2, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + + QuarkBuilder.BuilderResult memory result = builder.morphoRepay( + repayIntent_( + 8453, + "USDC", // repaying 2 USDC, bridged from mainnet to base + 2e6, + "WETH", + 0e18 + ), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) + ); + + address paycallAddress = paycallUsdc_(1); + address paycallAddressBase = paycallUsdc_(8453); + address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + + assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + // Check the quark operations + // first operation + assertEq(result.quarkOperations.length, 2, "two operations"); + assertEq( + result.quarkOperations[0].scriptAddress, + paycallAddress, + "script address is correct given the code jar address on base" + ); + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + cctpBridgeActionsAddress, + abi.encodeWithSelector( + CCTPBridgeActions.bridgeUSDC.selector, + address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), + 2.2e6, // 2e6 repaid + 0.2e6 max cost on Base + 6, + bytes32(uint256(uint160(0xa11ce))), + usdc_(1) + ), + 0.1e6 + ), + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 2.2e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 2); + assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); + assertEq( + result.quarkOperations[0].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // second operation + assertEq( + result.quarkOperations[1].scriptAddress, + paycallAddressBase, + "script address[1] has been wrapped with paycall address" + ); + + assertEq( + result.quarkOperations[1].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + morphoBlueActionsAddress, + abi.encodeCall( + MorphoBlueActions.repayAndWithdrawCollateral, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(8453, "WETH", "USDC"), + 2e6, + 0, + 0e18, + address(0xa11ce), + address(0xa11ce) + ) + ), + 0.2e6 + ), + "calldata is Paycall.run(MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e6, 0, 0e18, address(0xa11ce), address(0xa11ce));" + ); + assertEq(result.quarkOperations[1].scriptSources.length, 2); + assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[1].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) + ); + assertEq( + result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // Check the actions + assertEq(result.actions.length, 2, "two actions"); + // first action + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'"); + assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); + assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.BridgeActionContext({ + amount: 2.2e6, + assetSymbol: "USDC", + bridgeType: Actions.BRIDGE_TYPE_CCTP, + chainId: 1, + destinationChainId: 8453, + price: USDC_PRICE, + recipient: address(0xa11ce), + token: usdc_(1) + }) + ), + "action context encoded from BridgeActionContext" + ); + // second action + assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); + assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); + assertEq(result.actions[1].paymentMaxCost, 0.2e6, "payment should have max cost of 0.2e6"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 0e18; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WETH_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = weth_(8453); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WETH"; + + assertEq( + result.actions[1].actionContext, + abi.encode( + Actions.RepayActionContext({ + amount: 2e6, + assetSymbol: "USDC", + chainId: 8453, + collateralAmounts: collateralAmounts, + collateralAssetSymbols: collateralAssetSymbols, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + comet: address(0), + price: USDC_PRICE, + token: usdc_(8453), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "WETH", "USDC")) + }) + ), + "action context encoded from RepayActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testCometRepayMax() public { + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + + MorphoBluePortfolio[] memory morphoBluePortfolios = new MorphoBluePortfolio[](1); + morphoBluePortfolios[0] = MorphoBluePortfolio({ + marketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")), + loanToken: "USDC", + collateralToken: "WBTC", + borrowedBalance: 10e6, + borrowedShares: 5e18, // Random shares number for unit test + collateralBalance: 1e8 + }); + + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](1); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(20e6, 0, 0, 0), // has 20 USDC + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: morphoBluePortfolios + }); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoRepay( + repayIntent_( + 1, + "USDC", + type(uint256).max, // repaying max (all 10 USDC) + "WBTC", + 0e8 // no collateral withdrawal + ), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) + ); + + assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + address paycallAddress = CodeJarHelper.getCodeAddress( + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + + // Check the quark operations + assertEq(result.quarkOperations.length, 1, "one operation"); + assertEq( + result.quarkOperations[0].scriptAddress, + paycallAddress, + "script address is correct given the code jar address on mainnet" + ); + + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + morphoBlueActionsAddress, + abi.encodeCall( + MorphoBlueActions.repayAndWithdrawCollateral, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(1, "WBTC", "USDC"), + 0e6, + 5e18, + 0e8, + address(0xa11ce), + address(0xa11ce) + ) // Repaying in shares + ), + 0.1e6 + ), + "calldata is Paycall.run(MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 0e6, 5e18, 0e8, address(0xa11ce), address(0xa11ce));" + ); + + assertEq(result.quarkOperations[0].scriptSources.length, 2); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[0].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + assertEq(result.actions.length, 1, "one action"); + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); + assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 0e8; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WBTC_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = wbtc_(1); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WBTC"; + + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.RepayActionContext({ + amount: type(uint256).max, + assetSymbol: "USDC", + chainId: 1, + collateralAmounts: collateralAmounts, + collateralAssetSymbols: collateralAssetSymbols, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + comet: address(0), + price: USDC_PRICE, + token: usdc_(1), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) + }) + ), + "action context encoded from RepayActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } + + function testCometRepayMaxWithBridge() public { + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](2); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); + maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 0.1e6}); + + MorphoBluePortfolio[] memory morphoBluePortfolios = new MorphoBluePortfolio[](1); + morphoBluePortfolios[0] = MorphoBluePortfolio({ + marketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "WETH", "USDC")), + loanToken: "USDC", + collateralToken: "WETH", + borrowedBalance: 10e6, + borrowedShares: 5e18, // Random shares number for unit test + collateralBalance: 1e8 + }); + + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: emptyMorphoBluePortfolios_() + }); + chainPortfolios[1] = ChainPortfolio({ + chainId: 8453, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base + cometPortfolios: emptyCometPortfolios_(), + morphoBluePortfolios: morphoBluePortfolios + }); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.morphoRepay( + repayIntent_( + 8453, + "USDC", + type(uint256).max, // repaying max (all 10 USDC) + "WETH", + 0e18 + ), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) + ); + + assertEq(result.paymentCurrency, "usdc", "usdc currency"); + + address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); + address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address paycallAddress = CodeJarHelper.getCodeAddress( + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + address paycallAddressBase = CodeJarHelper.getCodeAddress( + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) + ); + + // Check the quark operations + // first operation + assertEq(result.quarkOperations.length, 2, "two operations"); + assertEq( + result.quarkOperations[0].scriptAddress, + paycallAddress, + "script address is correct given the code jar address on base" + ); + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + cctpBridgeActionsAddress, + abi.encodeWithSelector( + CCTPBridgeActions.bridgeUSDC.selector, + address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), + 10.11e6, // 10e6 repaid + .1% buffer + 0.1e6 max cost on Base + 6, + bytes32(uint256(uint160(0xa11ce))), + usdc_(1) + ), + 0.1e6 + ), + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 10.11e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + ); + assertEq(result.quarkOperations[0].scriptSources.length, 2); + assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); + assertEq( + result.quarkOperations[0].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) + ); + assertEq( + result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // second operation + assertEq( + result.quarkOperations[1].scriptAddress, + paycallAddressBase, + "script address[1] has been wrapped with paycall address" + ); + assertEq( + result.quarkOperations[1].scriptCalldata, + abi.encodeWithSelector( + Paycall.run.selector, + morphoBlueActionsAddress, + abi.encodeCall( + MorphoBlueActions.repayAndWithdrawCollateral, + ( + MorphoInfo.getMorphoAddress(), + MorphoInfo.getMarketParams(8453, "WETH", "USDC"), + 0e6, + 5e18, + 0e8, + address(0xa11ce), + address(0xa11ce) + ) // Repaying in shares + ), + 0.1e6 + ), + "calldata is Paycall.run(MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 0e6, 5e18, 0e8, address(0xa11ce), address(0xa11ce));" + ); + + assertEq(result.quarkOperations[1].scriptSources.length, 2); + assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq( + result.quarkOperations[1].scriptSources[1], + abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) + ); + assertEq( + result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" + ); + + // check the actions + // first action + assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'"); + assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); + assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq( + result.actions[0].actionContext, + abi.encode( + Actions.BridgeActionContext({ + amount: 10.11e6, + assetSymbol: "USDC", + bridgeType: Actions.BRIDGE_TYPE_CCTP, + chainId: 1, + destinationChainId: 8453, + price: USDC_PRICE, + recipient: address(0xa11ce), + token: USDC_1 + }) + ), + "action context encoded from BridgeActionContext" + ); + + // second action + assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); + assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); + assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); + assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 0e18; + uint256[] memory collateralTokenPrices = new uint256[](1); + collateralTokenPrices[0] = WETH_PRICE; + address[] memory collateralTokens = new address[](1); + collateralTokens[0] = weth_(8453); + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "WETH"; + + assertEq( + result.actions[1].actionContext, + abi.encode( + Actions.RepayActionContext({ + amount: type(uint256).max, + assetSymbol: "USDC", + chainId: 8453, + collateralAmounts: collateralAmounts, + collateralAssetSymbols: collateralAssetSymbols, + collateralTokenPrices: collateralTokenPrices, + collateralTokens: collateralTokens, + comet: address(0), + price: USDC_PRICE, + token: usdc_(8453), + morpho: MorphoInfo.getMorphoAddress(), + morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "WETH", "USDC")) + }) + ), + "action context encoded from RepayActionContext" + ); + + // TODO: Check the contents of the EIP712 data + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); + assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); + assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); + } } From 355bb766dc89780edb409e68e39989adafafeb61 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 28 Aug 2024 17:28:36 -0700 Subject: [PATCH 24/50] add withdraw collateral --- test/builder/QuarkBuilderMorphoRepay.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index bf97c4a3..d3eafadc 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -102,7 +102,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { "USDC", 1e6, // repaying 1 USDC "WBTC", - 0e8 + 1e8 // withdraw WBTC ), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsd_() @@ -141,12 +141,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, - 0e8, + 1e8, address(0xa11ce), address(0xa11ce) ) ), - "calldata is MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 0e8, address(0xa11ce), address(0xa11ce));" + "calldata is MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 1e8, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 1); assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); @@ -164,7 +164,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 0e8; + collateralAmounts[0] = 1e8; uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = WBTC_PRICE; address[] memory collateralTokens = new address[](1); From 8e53e39ff61258d0bef259589f8aa66ebc89a705 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 29 Aug 2024 11:18:29 -0700 Subject: [PATCH 25/50] adjust code for moving defiscripts code to p1 pr --- src/builder/Actions.sol | 6 ++---- test/builder/QuarkBuilderMorphoBorrow.t.sol | 2 +- test/builder/QuarkBuilderMorphoRepay.t.sol | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 77706b7a..43280395 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -12,11 +12,9 @@ import { CometSupplyActions, CometSupplyMultipleAssetsAndBorrow, CometWithdrawActions, - TransferActions, - MorphoBlueActions, - MorphoRewardsActions, - MorphoVaultActions + TransferActions } from "../DeFiScripts.sol"; +import {MorphoBlueActions, MorphoRewardsActions, MorphoVaultActions} from "../defi_integrations/MorphoScripts.sol"; import {WrapperActions} from "../WrapperScripts.sol"; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; import {IMorpho, Position} from "../interfaces/IMorpho.sol"; diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index d92198e7..941bc7ed 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -10,7 +10,7 @@ import {QuarkBuilderTest, Accounts, PaymentInfo, QuarkBuilder} from "test/builde import {Actions} from "src/builder/Actions.sol"; import {CCTPBridgeActions} from "src/BridgeScripts.sol"; import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; -import {MorphoBlueActions} from "src/DeFiScripts.sol"; +import {MorphoBlueActions} from "src/defi_integrations/MorphoScripts.sol"; import {Paycall} from "src/Paycall.sol"; import {Strings} from "src/builder/Strings.sol"; import {Multicall} from "src/Multicall.sol"; diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index d3eafadc..0799a563 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -9,7 +9,7 @@ import {QuarkBuilderTest, Accounts, PaymentInfo} from "test/builder/lib/QuarkBui import {Actions} from "src/builder/Actions.sol"; import {CCTPBridgeActions} from "src/BridgeScripts.sol"; import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; -import {MorphoBlueActions} from "src/DeFiScripts.sol"; +import {MorphoBlueActions} from "src/defi_integrations/MorphoScripts.sol"; import {Paycall} from "src/Paycall.sol"; import {Strings} from "src/builder/Strings.sol"; import {Multicall} from "src/Multicall.sol"; From b8280ac8153970290e625dcbcf3b23cfd1c8b0dc Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 04:23:58 -0700 Subject: [PATCH 26/50] since max is handled by the morphoscripts, don't need to pass in shares no more --- src/builder/Actions.sol | 36 ++---- src/builder/QuarkBuilder.sol | 4 +- test/builder/QuarkBuilderMorphoBorrow.t.sol | 95 +++++----------- test/builder/QuarkBuilderMorphoRepay.t.sol | 120 +++++++------------- 4 files changed, 84 insertions(+), 171 deletions(-) diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 43280395..6b84337b 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -14,7 +14,7 @@ import { CometWithdrawActions, TransferActions } from "../DeFiScripts.sol"; -import {MorphoBlueActions, MorphoRewardsActions, MorphoVaultActions} from "../defi_integrations/MorphoScripts.sol"; +import {MorphoActions, MorphoRewardsActions, MorphoVaultActions} from "../MorphoScripts.sol"; import {WrapperActions} from "../WrapperScripts.sol"; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; import {IMorpho, Position} from "../interfaces/IMorpho.sol"; @@ -168,9 +168,6 @@ library Actions { uint256 blockTimestamp; uint256 collateralAmount; string collateralAssetSymbol; - // Needed for handling max repay - bool payInShares; - uint256 borrowedShares; } // Note: Mainly to avoid stack too deep errors @@ -860,7 +857,7 @@ library Actions { returns (IQuarkWallet.QuarkOperation memory, Action memory) { bytes[] memory scriptSources = new bytes[](1); - scriptSources[0] = type(MorphoBlueActions).creationCode; + scriptSources[0] = type(MorphoActions).creationCode; Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(borrowInput.chainId, borrowInput.chainAccountsList); @@ -874,19 +871,17 @@ library Actions { Accounts.findAssetPositions(borrowInput.collateralAssetSymbol, accounts.assetPositionsList); bytes memory scriptCalldata = abi.encodeWithSelector( - MorphoBlueActions.supplyCollateralAndBorrow.selector, + MorphoActions.supplyCollateralAndBorrow.selector, MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.collateralAssetSymbol, borrowInput.assetSymbol), borrowInput.collateralAmount, - borrowInput.amount, - borrowInput.borrower, - borrowInput.borrower + borrowInput.amount ); // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ nonce: accountState.quarkNextNonce, - scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode), + scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, expiry: borrowInput.blockTimestamp + STANDARD_EXPIRY_BUFFER @@ -941,7 +936,7 @@ library Actions { returns (IQuarkWallet.QuarkOperation memory, Action memory) { bytes[] memory scriptSources = new bytes[](1); - scriptSources[0] = type(MorphoBlueActions).creationCode; + scriptSources[0] = type(MorphoActions).creationCode; Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(repayInput.chainId, repayInput.chainAccountsList); @@ -954,28 +949,19 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(repayInput.collateralAssetSymbol, accounts.assetPositionsList); - uint256 repayAmount = repayInput.amount; - uint256 repayShares = 0; - - if (repayInput.payInShares) { - repayAmount = 0; - repayShares = repayInput.borrowedShares; - } bytes memory scriptCalldata = abi.encodeWithSelector( - MorphoBlueActions.repayAndWithdrawCollateral.selector, + MorphoActions.repayAndWithdrawCollateral.selector, MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(repayInput.chainId, repayInput.collateralAssetSymbol, repayInput.assetSymbol), - repayAmount, - repayShares, - repayInput.collateralAmount, - repayInput.repayer, - repayInput.repayer + repayInput.amount, + 0, + repayInput.collateralAmount ); // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ nonce: accountState.quarkNextNonce, - scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode), + scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, expiry: repayInput.blockTimestamp + STANDARD_EXPIRY_BUFFER diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index 89cc777e..b571999c 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1259,9 +1259,7 @@ contract QuarkBuilder { repayer: repayIntent.repayer, blockTimestamp: repayIntent.blockTimestamp, collateralAmount: repayIntent.collateralAmount, - collateralAssetSymbol: repayIntent.collateralAssetSymbol, - payInShares: isMaxRepay, - borrowedShares: repayShares + collateralAssetSymbol: repayIntent.collateralAssetSymbol }), payment ); diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index 941bc7ed..ad659f1f 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -10,7 +10,7 @@ import {QuarkBuilderTest, Accounts, PaymentInfo, QuarkBuilder} from "test/builde import {Actions} from "src/builder/Actions.sol"; import {CCTPBridgeActions} from "src/BridgeScripts.sol"; import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; -import {MorphoBlueActions} from "src/defi_integrations/MorphoScripts.sol"; +import {MorphoActions} from "src/MorphoScripts.sol"; import {Paycall} from "src/Paycall.sol"; import {Strings} from "src/builder/Strings.sol"; import {Multicall} from "src/Multicall.sol"; @@ -120,7 +120,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { address(CodeJarHelper.CODE_JAR_ADDRESS), uint256(0), /* script bytecode */ - keccak256(type(MorphoBlueActions).creationCode) + keccak256(type(MorphoActions).creationCode) ) ) ) @@ -131,20 +131,13 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].scriptCalldata, abi.encodeCall( - MorphoBlueActions.supplyCollateralAndBorrow, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(1, "WBTC", "USDC"), - 1e8, - 1e6, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.supplyCollateralAndBorrow, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) ), - "calldata is MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xal1ce), address(0xal1ce));" + "calldata is MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xal1ce), address(0xal1ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 1); - assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); @@ -226,7 +219,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { address multicallAddress = CodeJarHelper.getCodeAddress(type(Multicall).creationCode); address wrapperActionsAddress = CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one merged operation"); assertEq( @@ -236,31 +229,24 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ); address[] memory callContracts = new address[](2); callContracts[0] = wrapperActionsAddress; - callContracts[1] = morphoBlueActionsAddress; + callContracts[1] = MorphoActionsAddress; bytes[] memory callDatas = new bytes[](2); callDatas[0] = abi.encodeWithSelector( WrapperActions.wrapETH.selector, TokenWrapper.getKnownWrapperTokenPair(8453, "WETH").wrapper, 1e18 ); callDatas[1] = abi.encodeCall( - MorphoBlueActions.supplyCollateralAndBorrow, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(8453, "WETH", "USDC"), - 1e18, - 1e6, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.supplyCollateralAndBorrow, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 1e18, 1e6) ); assertEq( result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), - "calldata is Multicall.run([wrapperActionsAddress, morphoBlueActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 10e18), MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 1e6, address(0xa11ce), address(0xa11ce))" + "calldata is Multicall.run([wrapperActionsAddress, MorphoActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 10e18), MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 1e6, address(0xa11ce), address(0xa11ce))" ); assertEq(result.quarkOperations[0].scriptSources.length, 3); assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); - assertEq(result.quarkOperations[0].scriptSources[1], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[1], type(MorphoActions).creationCode); assertEq(result.quarkOperations[0].scriptSources[2], type(Multicall).creationCode); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" @@ -340,7 +326,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { paymentUsdc_(maxCosts) ); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); address paycallAddress = paycallUsdc_(1); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -357,24 +343,17 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - morphoBlueActionsAddress, + MorphoActionsAddress, abi.encodeCall( - MorphoBlueActions.supplyCollateralAndBorrow, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(1, "WBTC", "USDC"), - 1e8, - 1e6, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.supplyCollateralAndBorrow, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) ), 0.1e6 ), - "calldata is Paycall.run(MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); - assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[0].scriptSources[1], abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) @@ -459,7 +438,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { paymentUsdc_(maxCosts) ); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); address paycallAddress = paycallUsdc_(1); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -476,24 +455,17 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - morphoBlueActionsAddress, + MorphoActionsAddress, abi.encodeCall( - MorphoBlueActions.supplyCollateralAndBorrow, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(1, "WBTC", "USDC"), - 1e8, - 1e6, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.supplyCollateralAndBorrow, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) ), 0.1e6 ), - "calldata is Paycall.run(MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); - assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[0].scriptSources[1], abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) @@ -582,7 +554,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { address paycallAddress = paycallUsdc_(1); address paycallAddressBase = paycallUsdc_(8453); address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -632,24 +604,17 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - morphoBlueActionsAddress, + MorphoActionsAddress, abi.encodeCall( - MorphoBlueActions.supplyCollateralAndBorrow, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), - 1e18, - 0.2e18, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.supplyCollateralAndBorrow, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), 1e18, 0.2e18) ), 1e6 ), - "calldata is Paycall.run(MorphoBlueActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, cbETH, WETH), 1e18, 0.2e18, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, cbETH, WETH), 1e18, 0.2e18, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[1].scriptSources.length, 2); - assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[1].scriptSources[1], abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 0799a563..9a1ff5af 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -9,7 +9,7 @@ import {QuarkBuilderTest, Accounts, PaymentInfo} from "test/builder/lib/QuarkBui import {Actions} from "src/builder/Actions.sol"; import {CCTPBridgeActions} from "src/BridgeScripts.sol"; import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; -import {MorphoBlueActions} from "src/defi_integrations/MorphoScripts.sol"; +import {MorphoActions} from "src/MorphoScripts.sol"; import {Paycall} from "src/Paycall.sol"; import {Strings} from "src/builder/Strings.sol"; import {Multicall} from "src/Multicall.sol"; @@ -124,7 +124,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address(CodeJarHelper.CODE_JAR_ADDRESS), uint256(0), /* script bytecode */ - keccak256(type(MorphoBlueActions).creationCode) + keccak256(type(MorphoActions).creationCode) ) ) ) @@ -135,21 +135,13 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].scriptCalldata, abi.encodeCall( - MorphoBlueActions.repayAndWithdrawCollateral, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(1, "WBTC", "USDC"), - 1e6, - 0, - 1e8, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.repayAndWithdrawCollateral, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, 1e8) ), - "calldata is MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 1e8, address(0xa11ce), address(0xa11ce));" + "calldata is MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 1e8, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 1); - assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); @@ -237,7 +229,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address multicallAddress = CodeJarHelper.getCodeAddress(type(Multicall).creationCode); address wrapperActionsAddress = CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one merged operation"); @@ -248,32 +240,24 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); address[] memory callContracts = new address[](2); callContracts[0] = wrapperActionsAddress; - callContracts[1] = morphoBlueActionsAddress; + callContracts[1] = MorphoActionsAddress; bytes[] memory callDatas = new bytes[](2); callDatas[0] = abi.encodeWithSelector( WrapperActions.wrapETH.selector, TokenWrapper.getKnownWrapperTokenPair(8453, "WETH").wrapper, 1e18 ); callDatas[1] = abi.encodeCall( - MorphoBlueActions.repayAndWithdrawCollateral, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), - 1e18, - 0, - 0e18, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.repayAndWithdrawCollateral, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), 1e18, 0, 0e18) ); assertEq( result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), - "calldata is Multicall.run([wrapperActionsAddress, morphoBlueActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 0, 0e18, address(0xa11ce), address(0xa11ce))" + "calldata is Multicall.run([wrapperActionsAddress, MorphoActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 0, 0e18, address(0xa11ce), address(0xa11ce))" ); assertEq(result.quarkOperations[0].scriptSources.length, 3); assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); - assertEq(result.quarkOperations[0].scriptSources[1], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[1], type(MorphoActions).creationCode); assertEq(result.quarkOperations[0].scriptSources[2], type(Multicall).creationCode); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" @@ -361,7 +345,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { paymentUsdc_(maxCosts) // and paying with USDC ); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); address paycallAddress = paycallUsdc_(1); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -377,25 +361,17 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - morphoBlueActionsAddress, + MorphoActionsAddress, abi.encodeCall( - MorphoBlueActions.repayAndWithdrawCollateral, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(1, "WBTC", "USDC"), - 1e6, - 0, - 0e8, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.repayAndWithdrawCollateral, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, 0e8) ), 0.1e6 ), - "calldata is Paycall.run(MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 0e8, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 0);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); - assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[0].scriptSources[1], abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) @@ -491,7 +467,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address paycallAddress = paycallUsdc_(1); address paycallAddressBase = paycallUsdc_(8453); address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -541,25 +517,17 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - morphoBlueActionsAddress, + MorphoActionsAddress, abi.encodeCall( - MorphoBlueActions.repayAndWithdrawCollateral, - ( - MorphoInfo.getMorphoAddress(), - MorphoInfo.getMarketParams(8453, "WETH", "USDC"), - 2e6, - 0, - 0e18, - address(0xa11ce), - address(0xa11ce) - ) + MorphoActions.repayAndWithdrawCollateral, + (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 2e6, 0, 0e18) ), 0.2e6 ), - "calldata is Paycall.run(MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e6, 0, 0e18, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 2e6, 0, 0);" ); assertEq(result.quarkOperations[1].scriptSources.length, 2); - assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[1].scriptSources[1], abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) @@ -680,7 +648,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address paycallAddress = CodeJarHelper.getCodeAddress( abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) ); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one operation"); @@ -694,26 +662,24 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - morphoBlueActionsAddress, + MorphoActionsAddress, abi.encodeCall( - MorphoBlueActions.repayAndWithdrawCollateral, + MorphoActions.repayAndWithdrawCollateral, ( MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), - 0e6, - 5e18, - 0e8, - address(0xa11ce), - address(0xa11ce) - ) // Repaying in shares + type(uint256).max, + 0, + 0 + ) ), 0.1e6 ), - "calldata is Paycall.run(MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 0e6, 5e18, 0e8, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), type(uint256).max, 0, 0);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); - assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[0].scriptSources[1], abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) @@ -809,7 +775,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { "USDC", type(uint256).max, // repaying max (all 10 USDC) "WETH", - 0e18 + 0 ), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) @@ -818,7 +784,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.paymentCurrency, "usdc", "usdc currency"); address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - address morphoBlueActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoBlueActions).creationCode); + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); address paycallAddress = CodeJarHelper.getCodeAddress( abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) ); @@ -871,26 +837,24 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - morphoBlueActionsAddress, + MorphoActionsAddress, abi.encodeCall( - MorphoBlueActions.repayAndWithdrawCollateral, + MorphoActions.repayAndWithdrawCollateral, ( MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), - 0e6, - 5e18, - 0e8, - address(0xa11ce), - address(0xa11ce) + type(uint256).max, + 0, + 0 ) // Repaying in shares ), 0.1e6 ), - "calldata is Paycall.run(MorphoBlueActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 0e6, 5e18, 0e8, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), type(uint256).max, 0, 0);" ); assertEq(result.quarkOperations[1].scriptSources.length, 2); - assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoBlueActions).creationCode); + assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoActions).creationCode); assertEq( result.quarkOperations[1].scriptSources[1], abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_8453, USDC_8453)) From bbcd6e9d172a73f2d2f6493d668fa728b4bb78d2 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 05:44:45 -0700 Subject: [PATCH 27/50] change names to drop Blue, and update tests to reflect the new no-shares approach --- src/builder/Accounts.sol | 38 ++++++------ src/builder/QuarkBuilder.sol | 11 ++-- test/builder/QuarkBuilderCometBorrow.t.sol | 24 ++++---- test/builder/QuarkBuilderCometRepay.t.sol | 28 ++++----- test/builder/QuarkBuilderCometSupply.t.sol | 4 +- test/builder/QuarkBuilderCometWithdraw.t.sol | 8 +-- test/builder/QuarkBuilderMorphoBorrow.t.sol | 24 ++++---- test/builder/QuarkBuilderMorphoRepay.t.sol | 34 +++++------ test/builder/QuarkBuilderSwap.t.sol | 8 +-- test/builder/QuarkBuilderTransfer.t.sol | 20 +++---- test/builder/lib/QuarkBuilderTest.sol | 63 ++++++++++---------- 11 files changed, 127 insertions(+), 135 deletions(-) diff --git a/src/builder/Accounts.sol b/src/builder/Accounts.sol index 4630e2f0..773f6eae 100644 --- a/src/builder/Accounts.sol +++ b/src/builder/Accounts.sol @@ -13,7 +13,7 @@ library Accounts { QuarkState[] quarkStates; AssetPositions[] assetPositionsList; CometPositions[] cometPositions; - MorphoBluePositions[] morphoBluePositions; + MorphoPositions[] morphoPositions; } // We map this to the Portfolio data structure that the client will already have. @@ -58,22 +58,21 @@ library Accounts { uint256[] balances; } - struct MorphoBluePositions { + struct MorphoPositions { bytes32 marketId; address morpho; address loanToken; address collateralToken; - MorphoBlueBorrowPosition borrowPosition; - MorphoBlueCollateralPosition collateralPosition; + MorphoBorrowPosition borrowPosition; + MorphoCollateralPosition collateralPosition; } - struct MorphoBlueBorrowPosition { + struct MorphoBorrowPosition { address[] accounts; uint256[] borrowedBalances; - uint256[] borrowedShares; } - struct MorphoBlueCollateralPosition { + struct MorphoCollateralPosition { address[] accounts; uint256[] collateralBalances; } @@ -103,19 +102,19 @@ library Accounts { } } - function findMorphoBluePositions( + function findMorphoPositions( uint256 chainId, address loanToken, address collateralToken, ChainAccounts[] memory chainAccountsList - ) internal pure returns (MorphoBluePositions memory found) { + ) internal pure returns (MorphoPositions memory found) { ChainAccounts memory chainAccounts = findChainAccounts(chainId, chainAccountsList); - for (uint256 i = 0; i < chainAccounts.morphoBluePositions.length; ++i) { + for (uint256 i = 0; i < chainAccounts.morphoPositions.length; ++i) { if ( - chainAccounts.morphoBluePositions[i].loanToken == loanToken - && chainAccounts.morphoBluePositions[i].collateralToken == collateralToken + chainAccounts.morphoPositions[i].loanToken == loanToken + && chainAccounts.morphoPositions[i].collateralToken == collateralToken ) { - return found = chainAccounts.morphoBluePositions[i]; + return found = chainAccounts.morphoPositions[i]; } } } @@ -298,13 +297,12 @@ library Accounts { address loanToken, address collateralToken, address account - ) internal pure returns (uint256 totalBorrow, uint256 totalBorrowedShares) { - Accounts.MorphoBluePositions memory morphoBluePositions = - findMorphoBluePositions(chainId, loanToken, collateralToken, chainAccountsList); - for (uint256 i = 0; i < morphoBluePositions.borrowPosition.accounts.length; ++i) { - if (morphoBluePositions.borrowPosition.accounts[i] == account) { - totalBorrow = morphoBluePositions.borrowPosition.borrowedBalances[i]; - totalBorrowedShares = morphoBluePositions.borrowPosition.borrowedShares[i]; + ) internal pure returns (uint256 totalBorrow) { + Accounts.MorphoPositions memory morphoPositions = + findMorphoPositions(chainId, loanToken, collateralToken, chainAccountsList); + for (uint256 i = 0; i < morphoPositions.borrowPosition.accounts.length; ++i) { + if (morphoPositions.borrowPosition.accounts[i] == account) { + totalBorrow = morphoPositions.borrowPosition.borrowedBalances[i]; } } } diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index b571999c..bcf91f53 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -82,12 +82,11 @@ contract QuarkBuilder { address loanToken, address collateralToken, address repayer - ) internal pure returns (uint256 amount, uint256 shares) { - (uint256 totalBorrowForAccount, uint256 totalBorrowedSharesForAccount) = + ) internal pure returns (uint256 amount) { + uint256 totalBorrowForAccount = Accounts.totalMorphoBorrowForAccount(chainAccountsList, chainId, loanToken, collateralToken, repayer); uint256 buffer = totalBorrowForAccount / 1000; // 0.1% amount = totalBorrowForAccount + buffer; - shares = totalBorrowedSharesForAccount; } function cometRepay( @@ -1188,10 +1187,10 @@ contract QuarkBuilder { bool isMaxRepay = repayIntent.amount == type(uint256).max; bool useQuotecall = false; // never use Quotecall + // Only use repayAmount for purpose of bridging, will still use uint256 max for MorphoScript uint256 repayAmount = repayIntent.amount; - uint256 repayShares = 0; // Default to be 0 and not used, unless mas repay case if (isMaxRepay) { - (repayAmount, repayShares) = morphorRepayMaxAmount( + repayAmount = morphorRepayMaxAmount( chainAccountsList, repayIntent.chainId, Accounts.findAssetPositions(repayIntent.assetSymbol, repayIntent.chainId, chainAccountsList).asset, @@ -1548,7 +1547,7 @@ contract QuarkBuilder { ); } else if (repayActionContext.morpho != address(0)) { // Morpho repay - (repayAmount,) = morphorRepayMaxAmount( + repayAmount = morphorRepayMaxAmount( chainAccountsList, repayActionContext.chainId, repayActionContext.token, diff --git a/test/builder/QuarkBuilderCometBorrow.t.sol b/test/builder/QuarkBuilderCometBorrow.t.sol index 439b449f..b1cbc2a6 100644 --- a/test/builder/QuarkBuilderCometBorrow.t.sol +++ b/test/builder/QuarkBuilderCometBorrow.t.sol @@ -88,7 +88,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -97,7 +97,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -213,7 +213,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 10e18, 0, 0), // user has 10 ETH cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -222,7 +222,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -324,7 +324,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 10e18, 0), // user has 1 USDC, 10 LINK cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -333,7 +333,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -454,7 +454,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK and 0 USDC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -463,7 +463,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometBorrow( @@ -581,7 +581,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(3e6, 0, 0, 0), // 3 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -590,7 +590,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 5e18, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometBorrow( @@ -769,7 +769,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -778,7 +778,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometBorrow( diff --git a/test/builder/QuarkBuilderCometRepay.t.sol b/test/builder/QuarkBuilderCometRepay.t.sol index 2e89fc87..66ef4524 100644 --- a/test/builder/QuarkBuilderCometRepay.t.sol +++ b/test/builder/QuarkBuilderCometRepay.t.sol @@ -87,7 +87,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 0, 1e18), // user does not have enough USDC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); vm.expectRevert(QuarkBuilder.MaxCostTooHigh.selector); @@ -114,7 +114,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -123,7 +123,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -240,7 +240,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 1e18, 0, 0), // has 1 ETH cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -249,7 +249,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -353,7 +353,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(2e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -362,7 +362,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -485,7 +485,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 1e18), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -494,7 +494,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometRepay( @@ -614,7 +614,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -623,7 +623,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.cometRepay( @@ -807,7 +807,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC cometPortfolios: cometPortfolios, - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -932,7 +932,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -941,7 +941,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base cometPortfolios: cometPortfolios, - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); diff --git a/test/builder/QuarkBuilderCometSupply.t.sol b/test/builder/QuarkBuilderCometSupply.t.sol index 5ed073da..872ddb5d 100644 --- a/test/builder/QuarkBuilderCometSupply.t.sol +++ b/test/builder/QuarkBuilderCometSupply.t.sol @@ -159,7 +159,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(3e6)), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); QuarkBuilder.BuilderResult memory result = builder.cometSupply( @@ -262,7 +262,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); QuarkBuilder.BuilderResult memory result = diff --git a/test/builder/QuarkBuilderCometWithdraw.t.sol b/test/builder/QuarkBuilderCometWithdraw.t.sol index 624831f7..2286435e 100644 --- a/test/builder/QuarkBuilderCometWithdraw.t.sol +++ b/test/builder/QuarkBuilderCometWithdraw.t.sol @@ -256,14 +256,14 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 3e6), // 3 USDC on mainnet cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 0), // 0 USDC on base cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); QuarkBuilder.BuilderResult memory result = builder.cometWithdraw( @@ -399,7 +399,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: cometPortfolios, - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -487,7 +487,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: cometPortfolios, - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index ad659f1f..fcddd31b 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -47,7 +47,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 1e18), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -56,7 +56,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); // Pair not exist in known Morpho markets @@ -87,7 +87,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 0), // user has 1 WBTC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -96,7 +96,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -196,7 +196,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -205,7 +205,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 10e18, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -304,7 +304,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 1e8, 0), // user has 1 WBTC and 1USDC for payment cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -313,7 +313,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -416,7 +416,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 0), // user has 1 WBTC but with 0 USDC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -425,7 +425,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -528,7 +528,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "cbETH", "WETH"), assetBalances: Arrays.uintArray(5e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -537,7 +537,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e18, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](2); diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 9a1ff5af..46003e09 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -62,7 +62,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 0, 1e18), // user does not have enough USDC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); vm.expectRevert(QuarkBuilder.MaxCostTooHigh.selector); @@ -83,7 +83,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -92,7 +92,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -200,7 +200,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "ETH", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -209,7 +209,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "ETH", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 1e18, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder builder = new QuarkBuilder(); @@ -317,7 +317,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(2e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -326,7 +326,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -440,7 +440,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -449,7 +449,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); QuarkBuilder.BuilderResult memory result = builder.morphoRepay( @@ -609,13 +609,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); - MorphoBluePortfolio[] memory morphoBluePortfolios = new MorphoBluePortfolio[](1); - morphoBluePortfolios[0] = MorphoBluePortfolio({ + MorphoPortfolio[] memory morphoPortfolios = new MorphoPortfolio[](1); + morphoPortfolios[0] = MorphoPortfolio({ marketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")), loanToken: "USDC", collateralToken: "WBTC", borrowedBalance: 10e6, - borrowedShares: 5e18, // Random shares number for unit test collateralBalance: 1e8 }); @@ -627,7 +626,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(20e6, 0, 0, 0), // has 20 USDC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: morphoBluePortfolios + morphoPortfolios: morphoPortfolios }); QuarkBuilder builder = new QuarkBuilder(); @@ -738,13 +737,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.1e6}); maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 0.1e6}); - MorphoBluePortfolio[] memory morphoBluePortfolios = new MorphoBluePortfolio[](1); - morphoBluePortfolios[0] = MorphoBluePortfolio({ + MorphoPortfolio[] memory morphoPortfolios = new MorphoPortfolio[](1); + morphoPortfolios[0] = MorphoPortfolio({ marketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "WETH", "USDC")), loanToken: "USDC", collateralToken: "WETH", borrowedBalance: 10e6, - borrowedShares: 5e18, // Random shares number for unit test collateralBalance: 1e8 }); @@ -756,7 +754,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: emptyMorphoBluePortfolios_() + morphoPortfolios: emptyMorphoPortfolios_() }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, @@ -765,7 +763,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base cometPortfolios: emptyCometPortfolios_(), - morphoBluePortfolios: morphoBluePortfolios + morphoPortfolios: morphoPortfolios }); QuarkBuilder builder = new QuarkBuilder(); diff --git a/test/builder/QuarkBuilderSwap.t.sol b/test/builder/QuarkBuilderSwap.t.sol index ba15b29c..ff49120e 100644 --- a/test/builder/QuarkBuilderSwap.t.sol +++ b/test/builder/QuarkBuilderSwap.t.sol @@ -249,7 +249,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); QuarkBuilder.BuilderResult memory result = builder.swap( buyUsdc_(1, weth_(1), 1e18, 3000e6, address(0xa11ce), BLOCK_TIMESTAMP), // swap 1 ETH on chain 1 to 3000 USDC @@ -407,21 +407,21 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(9005e6)), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), uint256(0)), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, quarkStates: quarkStates_(address(0xc0b), 5), assetPositionsList: assetPositionsList_(7777, address(0xc0b), uint256(0)), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); QuarkBuilder.BuilderResult memory result = builder.swap( diff --git a/test/builder/QuarkBuilderTransfer.t.sol b/test/builder/QuarkBuilderTransfer.t.sol index 3cd69895..368e409a 100644 --- a/test/builder/QuarkBuilderTransfer.t.sol +++ b/test/builder/QuarkBuilderTransfer.t.sol @@ -610,7 +610,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(10e6)), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); QuarkBuilder.BuilderResult memory result = builder.transfer( @@ -687,14 +687,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 8e6), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 4e6), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); QuarkBuilder.BuilderResult memory result = builder.transfer( @@ -824,21 +824,21 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 8e6), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 4e6), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, quarkStates: quarkStates_(address(0xfe11a), 2), assetPositionsList: assetPositionsList_(7777, address(0xfe11a), 5e6), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); // User has total holding of 17 USDC, but only 12 USDC is available for transfer/bridge to 8453, and missing 5 USDC stuck in random L2 so will revert with FundsUnavailable error @@ -926,7 +926,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); // Transfer 1.5ETH to 0xceecee on chain 1 @@ -1028,7 +1028,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); // Transfer 1.5ETH to 0xceecee on chain 1 @@ -1138,7 +1138,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); // Transfer max ETH to 0xceecee on chain 1 @@ -1249,7 +1249,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); // Transfer 1.5ETH to 0xceecee on chain 1 diff --git a/test/builder/lib/QuarkBuilderTest.sol b/test/builder/lib/QuarkBuilderTest.sol index 74a92f73..07a37a28 100644 --- a/test/builder/lib/QuarkBuilderTest.sol +++ b/test/builder/lib/QuarkBuilderTest.sol @@ -95,21 +95,21 @@ contract QuarkBuilderTest { quarkStates: quarkStates_(address(0xa11ce), 12), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(amount / 2)), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, quarkStates: quarkStates_(address(0xb0b), 2), assetPositionsList: assetPositionsList_(8453, address(0xb0b), uint256(amount / 2)), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, quarkStates: quarkStates_(address(0xc0b), 5), assetPositionsList: assetPositionsList_(7777, address(0xc0b), uint256(0)), cometPositions: emptyCometPositions_(), - morphoBluePositions: emptyMorphoBluePositions_() + morphoPositions: emptyMorphoPositions_() }); return chainAccountsList; } @@ -119,9 +119,9 @@ contract QuarkBuilderTest { return emptyCometPositions; } - function emptyMorphoBluePositions_() internal pure returns (Accounts.MorphoBluePositions[] memory) { - Accounts.MorphoBluePositions[] memory emptyMorphoBluePositions = new Accounts.MorphoBluePositions[](0); - return emptyMorphoBluePositions; + function emptyMorphoPositions_() internal pure returns (Accounts.MorphoPositions[] memory) { + Accounts.MorphoPositions[] memory emptyMorphoPositions = new Accounts.MorphoPositions[](0); + return emptyMorphoPositions; } function quarkStates_() internal pure returns (Accounts.QuarkState[] memory) { @@ -298,7 +298,7 @@ contract QuarkBuilderTest { string[] assetSymbols; uint256[] assetBalances; CometPortfolio[] cometPortfolios; - MorphoBluePortfolio[] morphoBluePortfolios; + MorphoPortfolio[] morphoPortfolios; } struct CometPortfolio { @@ -309,12 +309,11 @@ contract QuarkBuilderTest { uint256[] collateralAssetBalances; } - struct MorphoBluePortfolio { + struct MorphoPortfolio { bytes32 marketId; string loanToken; string collateralToken; uint256 borrowedBalance; - uint256 borrowedShares; uint256 collateralBalance; } @@ -323,9 +322,9 @@ contract QuarkBuilderTest { return emptyCometPortfolios; } - function emptyMorphoBluePortfolios_() internal pure returns (MorphoBluePortfolio[] memory) { - MorphoBluePortfolio[] memory emptyMorphoBluePortfolios = new MorphoBluePortfolio[](0); - return emptyMorphoBluePortfolios; + function emptyMorphoPortfolios_() internal pure returns (MorphoPortfolio[] memory) { + MorphoPortfolio[] memory emptyMorphoPortfolios = new MorphoPortfolio[](0); + return emptyMorphoPortfolios; } function chainAccountsFromChainPortfolios(ChainPortfolio[] memory chainPortfolios) @@ -348,8 +347,8 @@ contract QuarkBuilderTest { cometPositions: cometPositionsForCometPorfolios( chainPortfolios[i].chainId, chainPortfolios[i].account, chainPortfolios[i].cometPortfolios ), - morphoBluePositions: morphoBluePositionsForMorphoBluePortfolios( - chainPortfolios[i].chainId, chainPortfolios[i].account, chainPortfolios[i].morphoBluePortfolios + morphoPositions: morphoPositionsForMorphoPortfolios( + chainPortfolios[i].chainId, chainPortfolios[i].account, chainPortfolios[i].morphoPortfolios ) }); } @@ -393,37 +392,35 @@ contract QuarkBuilderTest { return cometPositions; } - function morphoBluePositionsForMorphoBluePortfolios( + function morphoPositionsForMorphoPortfolios( uint256 chainId, address account, - MorphoBluePortfolio[] memory morphoBluePortfolios - ) internal pure returns (Accounts.MorphoBluePositions[] memory) { - Accounts.MorphoBluePositions[] memory morphoBluePositions = - new Accounts.MorphoBluePositions[](morphoBluePortfolios.length); - - for (uint256 i = 0; i < morphoBluePortfolios.length; ++i) { - MorphoBluePortfolio memory morphoBluePortfolio = morphoBluePortfolios[i]; - (address loanAsset,,) = assetInfo(morphoBluePortfolio.loanToken, chainId); - (address collateralAsset,,) = assetInfo(morphoBluePortfolio.collateralToken, chainId); - - morphoBluePositions[i] = Accounts.MorphoBluePositions({ - marketId: morphoBluePortfolio.marketId, + MorphoPortfolio[] memory morphoPortfolios + ) internal pure returns (Accounts.MorphoPositions[] memory) { + Accounts.MorphoPositions[] memory morphoPositions = new Accounts.MorphoPositions[](morphoPortfolios.length); + + for (uint256 i = 0; i < morphoPortfolios.length; ++i) { + MorphoPortfolio memory morphoPortfolio = morphoPortfolios[i]; + (address loanAsset,,) = assetInfo(morphoPortfolio.loanToken, chainId); + (address collateralAsset,,) = assetInfo(morphoPortfolio.collateralToken, chainId); + + morphoPositions[i] = Accounts.MorphoPositions({ + marketId: morphoPortfolio.marketId, morpho: MorphoInfo.getMorphoAddress(), loanToken: loanAsset, collateralToken: collateralAsset, - borrowPosition: Accounts.MorphoBlueBorrowPosition({ + borrowPosition: Accounts.MorphoBorrowPosition({ accounts: Arrays.addressArray(account), - borrowedBalances: Arrays.uintArray(morphoBluePortfolio.borrowedBalance), - borrowedShares: Arrays.uintArray(morphoBluePortfolio.borrowedShares) + borrowedBalances: Arrays.uintArray(morphoPortfolio.borrowedBalance) }), - collateralPosition: Accounts.MorphoBlueCollateralPosition({ + collateralPosition: Accounts.MorphoCollateralPosition({ accounts: Arrays.addressArray(account), - collateralBalances: Arrays.uintArray(morphoBluePortfolio.collateralBalance) + collateralBalances: Arrays.uintArray(morphoPortfolio.collateralBalance) }) }); } - return morphoBluePositions; + return morphoPositions; } function baseAssetForComet(uint256 chainId, address comet) internal pure returns (address) { From 67c6ec52f867d21a7105762c4ca791a6e2fde593 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 06:23:38 -0700 Subject: [PATCH 28/50] rename assetPosition to collateralAssetPosition --- src/builder/Actions.sol | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 6b84337b..5db6a248 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -149,25 +149,25 @@ library Actions { } struct MorphoBorrow { - Accounts.ChainAccounts[] chainAccountsList; - string assetSymbol; uint256 amount; - uint256 chainId; - address borrower; + string assetSymbol; uint256 blockTimestamp; + address borrower; + Accounts.ChainAccounts[] chainAccountsList; + uint256 chainId; uint256 collateralAmount; string collateralAssetSymbol; } struct MorphoRepay { - Accounts.ChainAccounts[] chainAccountsList; - string assetSymbol; uint256 amount; - uint256 chainId; - address repayer; + string assetSymbol; uint256 blockTimestamp; + uint256 chainId; + Accounts.ChainAccounts[] chainAccountsList; uint256 collateralAmount; string collateralAssetSymbol; + address repayer; } // Note: Mainly to avoid stack too deep errors @@ -867,7 +867,7 @@ library Actions { Accounts.AssetPositions memory borrowAssetPositions = Accounts.findAssetPositions(borrowInput.assetSymbol, accounts.assetPositionsList); - Accounts.AssetPositions memory assetPositions = + Accounts.AssetPositions memory collateralAssetPositions = Accounts.findAssetPositions(borrowInput.collateralAssetSymbol, accounts.assetPositionsList); bytes memory scriptCalldata = abi.encodeWithSelector( @@ -893,9 +893,9 @@ library Actions { uint256[] memory collateralAmountsArray = new uint256[](1); collateralAmountsArray[0] = borrowInput.collateralAmount; uint256[] memory collateralTokenPricesArray = new uint256[](1); - collateralTokenPricesArray[0] = assetPositions.usdPrice; + collateralTokenPricesArray[0] = collateralAssetPositions.usdPrice; address[] memory collateralTokensArray = new address[](1); - collateralTokensArray[0] = assetPositions.asset; + collateralTokensArray[0] = collateralAssetPositions.asset; string[] memory collateralAssetSymbolsArray = new string[](1); collateralAssetSymbolsArray[0] = borrowInput.collateralAssetSymbol; @@ -946,7 +946,7 @@ library Actions { Accounts.AssetPositions memory repayAssetPositions = Accounts.findAssetPositions(repayInput.assetSymbol, accounts.assetPositionsList); - Accounts.AssetPositions memory assetPositions = + Accounts.AssetPositions memory collateralAssetPositions = Accounts.findAssetPositions(repayInput.collateralAssetSymbol, accounts.assetPositionsList); bytes memory scriptCalldata = abi.encodeWithSelector( @@ -975,9 +975,9 @@ library Actions { string[] memory collateralAssetSymbolsArray = new string[](1); collateralAssetSymbolsArray[0] = repayInput.collateralAssetSymbol; uint256[] memory collateralTokenPricesArray = new uint256[](1); - collateralTokenPricesArray[0] = assetPositions.usdPrice; + collateralTokenPricesArray[0] = collateralAssetPositions.usdPrice; address[] memory collateralTokensArray = new address[](1); - collateralTokensArray[0] = assetPositions.asset; + collateralTokensArray[0] = collateralAssetPositions.asset; RepayActionContext memory repayActionContext = RepayActionContext({ amount: repayInput.amount, From 215203bb6e29b03340ac37fcaf3edeb4df3fed00 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 06:26:45 -0700 Subject: [PATCH 29/50] add chainId check on getMorphoAddress --- src/builder/MorphoInfo.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol index 95c3623a..2eef49b4 100644 --- a/src/builder/MorphoInfo.sol +++ b/src/builder/MorphoInfo.sol @@ -10,8 +10,12 @@ library MorphoInfo { error MorphoVaultNotFound(); // Note: Current Morpho has same address across mainnet and base - function getMorphoAddress() internal pure returns (address) { - return 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + function getMorphoAddress(uint256 chainId) internal pure returns (address) { + if (chainId == 1 || chainId == 8453) { + return 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + } else { + revert UnsupportedChainId(); + } } // Morpho blue markets From 764ef103554b17e9f0949c65450340e2cfbd5463 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 06:27:12 -0700 Subject: [PATCH 30/50] typo --- src/builder/MorphoInfo.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol index 2eef49b4..8b3e938c 100644 --- a/src/builder/MorphoInfo.sol +++ b/src/builder/MorphoInfo.sol @@ -319,7 +319,7 @@ library MorphoInfo { } // Morpho vaults - // Note: Potentiall can add other key (i.e. curator) for supporting multiple vaults with same assets + // Note: Potential can add other key (i.e. curator) for supporting multiple vaults with same assets struct MorphoVaultKey { uint256 chainId; string supplyAssetSymbol; From 4df8d329f48e2af3e4e1cf635d5e4ccbf57f3ac3 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 06:28:33 -0700 Subject: [PATCH 31/50] typo morphor -> morpho --- src/builder/QuarkBuilder.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index bcf91f53..b768f3e6 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -76,7 +76,7 @@ contract QuarkBuilder { return totalBorrowForAccount + buffer; } - function morphorRepayMaxAmount( + function morphoRepayMaxAmount( Accounts.ChainAccounts[] memory chainAccountsList, uint256 chainId, address loanToken, @@ -1190,7 +1190,7 @@ contract QuarkBuilder { // Only use repayAmount for purpose of bridging, will still use uint256 max for MorphoScript uint256 repayAmount = repayIntent.amount; if (isMaxRepay) { - repayAmount = morphorRepayMaxAmount( + repayAmount = morphoRepayMaxAmount( chainAccountsList, repayIntent.chainId, Accounts.findAssetPositions(repayIntent.assetSymbol, repayIntent.chainId, chainAccountsList).asset, @@ -1547,7 +1547,7 @@ contract QuarkBuilder { ); } else if (repayActionContext.morpho != address(0)) { // Morpho repay - repayAmount = morphorRepayMaxAmount( + repayAmount = morphoRepayMaxAmount( chainAccountsList, repayActionContext.chainId, repayActionContext.token, From a93dcbff1232fab5069055882de34c4876aa1f63 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 07:46:09 -0700 Subject: [PATCH 32/50] add new action type, cuz builder code need that to distinguish between comet and morpho actions without having to store their own flag --- src/builder/Actions.sol | 106 ++++++----- src/builder/QuarkBuilder.sol | 33 ++-- test/builder/QuarkBuilderCometBorrow.t.sol | 24 +-- test/builder/QuarkBuilderCometRepay.t.sol | 28 +-- test/builder/QuarkBuilderMorphoBorrow.t.sol | 149 +++++---------- test/builder/QuarkBuilderMorphoRepay.t.sol | 171 ++++++------------ test/builder/lib/QuarkBuilderTest.sol | 2 +- .../MorphoInfo.t.sol | 2 +- 8 files changed, 191 insertions(+), 324 deletions(-) diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 5db6a248..4f7f99d1 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -26,10 +26,12 @@ import {List} from "./List.sol"; library Actions { /* ===== Constants ===== */ string constant ACTION_TYPE_BORROW = "BORROW"; + string constant ACTION_TYPE_MORPHO_BORROW = "MORPHO_BORROW"; string constant ACTION_TYPE_BRIDGE = "BRIDGE"; string constant ACTION_TYPE_CLAIM_REWARDS = "CLAIM_REWARDS"; string constant ACTION_TYPE_DRIP_TOKENS = "DRIP_TOKENS"; string constant ACTION_TYPE_REPAY = "REPAY"; + string constant ACTION_TYPE_MORPHO_REPAY = "MORPHO_REPAY"; string constant ACTION_TYPE_SUPPLY = "SUPPLY"; string constant ACTION_TYPE_SWAP = "SWAP"; string constant ACTION_TYPE_TRANSFER = "TRANSFER"; @@ -209,8 +211,6 @@ library Actions { address comet; uint256 price; address token; - address morpho; - bytes32 morphoMarketId; } struct BridgeActionContext { @@ -247,8 +247,6 @@ library Actions { address comet; uint256 price; address token; - address morpho; - bytes32 morphoMarketId; } struct SupplyActionContext { @@ -315,6 +313,34 @@ library Actions { string toAssetSymbol; } + struct MorphoRepayActionContext { + uint256 amount; + string assetSymbol; + uint256 chainId; + uint256 collateralAmount; + string collateralAssetSymbol; + uint256 collateralTokenPrice; + address collateralToken; + address morpho; + bytes32 morphoMarketId; + uint256 price; + address token; + } + + struct MorphoBorrowActionContext { + uint256 amount; + string assetSymbol; + uint256 chainId; + uint256 collateralAmount; + string collateralAssetSymbol; + uint256 collateralTokenPrice; + address collateralToken; + address morpho; + bytes32 morphoMarketId; + uint256 price; + address token; + } + function constructBridgeOperations( BridgeOperationInfo memory bridgeInfo, Accounts.ChainAccounts[] memory chainAccountsList, @@ -578,9 +604,7 @@ library Actions { collateralAssetSymbols: borrowInput.collateralAssetSymbols, comet: borrowInput.comet, price: borrowAssetPositions.usdPrice, - token: borrowAssetPositions.asset, - morpho: address(0), - morphoMarketId: bytes32(0) + token: borrowAssetPositions.asset }); Action memory action = Actions.Action({ chainId: borrowInput.chainId, @@ -653,9 +677,7 @@ library Actions { collateralTokens: collateralTokens, comet: repayInput.comet, price: repayAssetPositions.usdPrice, - token: repayAssetPositions.asset, - morpho: address(0), - morphoMarketId: bytes32(0) + token: repayAssetPositions.asset }); Action memory action = Actions.Action({ chainId: repayInput.chainId, @@ -872,7 +894,7 @@ library Actions { bytes memory scriptCalldata = abi.encodeWithSelector( MorphoActions.supplyCollateralAndBorrow.selector, - MorphoInfo.getMorphoAddress(), + MorphoInfo.getMorphoAddress(borrowInput.chainId), MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.collateralAssetSymbol, borrowInput.assetSymbol), borrowInput.collateralAmount, borrowInput.amount @@ -887,30 +909,17 @@ library Actions { expiry: borrowInput.blockTimestamp + STANDARD_EXPIRY_BUFFER }); - // Construct Action - - // Single element arrays to fit to existing context format - uint256[] memory collateralAmountsArray = new uint256[](1); - collateralAmountsArray[0] = borrowInput.collateralAmount; - uint256[] memory collateralTokenPricesArray = new uint256[](1); - collateralTokenPricesArray[0] = collateralAssetPositions.usdPrice; - address[] memory collateralTokensArray = new address[](1); - collateralTokensArray[0] = collateralAssetPositions.asset; - string[] memory collateralAssetSymbolsArray = new string[](1); - collateralAssetSymbolsArray[0] = borrowInput.collateralAssetSymbol; - - BorrowActionContext memory borrowActionContext = BorrowActionContext({ + MorphoBorrowActionContext memory borrowActionContext = MorphoBorrowActionContext({ assetSymbol: borrowInput.assetSymbol, amount: borrowInput.amount, chainId: borrowInput.chainId, - collateralAmounts: collateralAmountsArray, - collateralTokenPrices: collateralTokenPricesArray, - collateralTokens: collateralTokensArray, - collateralAssetSymbols: collateralAssetSymbolsArray, - comet: address(0), + collateralAmount: borrowInput.collateralAmount, + collateralTokenPrice: collateralAssetPositions.usdPrice, + collateralToken: collateralAssetPositions.asset, + collateralAssetSymbol: borrowInput.collateralAssetSymbol, price: borrowAssetPositions.usdPrice, token: borrowAssetPositions.asset, - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(borrowInput.chainId), morphoMarketId: MorphoInfo.marketId( MorphoInfo.getMarketParams(borrowInput.chainId, borrowInput.collateralAssetSymbol, borrowInput.assetSymbol) ) @@ -918,7 +927,7 @@ library Actions { Action memory action = Actions.Action({ chainId: borrowInput.chainId, quarkAccount: borrowInput.borrower, - actionType: ACTION_TYPE_BORROW, + actionType: ACTION_TYPE_MORPHO_BORROW, actionContext: abi.encode(borrowActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), // Null address for OFFCHAIN payment. @@ -951,7 +960,7 @@ library Actions { bytes memory scriptCalldata = abi.encodeWithSelector( MorphoActions.repayAndWithdrawCollateral.selector, - MorphoInfo.getMorphoAddress(), + MorphoInfo.getMorphoAddress(repayInput.chainId), MorphoInfo.getMarketParams(repayInput.chainId, repayInput.collateralAssetSymbol, repayInput.assetSymbol), repayInput.amount, 0, @@ -967,30 +976,17 @@ library Actions { expiry: repayInput.blockTimestamp + STANDARD_EXPIRY_BUFFER }); - // Construct Action - - // Single element arrays to fit to existing context format - uint256[] memory collateralAmountsArray = new uint256[](1); - collateralAmountsArray[0] = repayInput.collateralAmount; - string[] memory collateralAssetSymbolsArray = new string[](1); - collateralAssetSymbolsArray[0] = repayInput.collateralAssetSymbol; - uint256[] memory collateralTokenPricesArray = new uint256[](1); - collateralTokenPricesArray[0] = collateralAssetPositions.usdPrice; - address[] memory collateralTokensArray = new address[](1); - collateralTokensArray[0] = collateralAssetPositions.asset; - - RepayActionContext memory repayActionContext = RepayActionContext({ + MorphoRepayActionContext memory morphoRepayActionContext = MorphoRepayActionContext({ amount: repayInput.amount, assetSymbol: repayInput.assetSymbol, chainId: repayInput.chainId, - collateralAmounts: collateralAmountsArray, - collateralAssetSymbols: collateralAssetSymbolsArray, - collateralTokenPrices: collateralTokenPricesArray, - collateralTokens: collateralTokensArray, - comet: address(0), + collateralAmount: repayInput.collateralAmount, + collateralAssetSymbol: repayInput.collateralAssetSymbol, + collateralTokenPrice: collateralAssetPositions.usdPrice, + collateralToken: collateralAssetPositions.asset, price: repayAssetPositions.usdPrice, token: repayAssetPositions.asset, - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(repayInput.chainId), morphoMarketId: MorphoInfo.marketId( MorphoInfo.getMarketParams(repayInput.chainId, repayInput.collateralAssetSymbol, repayInput.assetSymbol) ) @@ -999,8 +995,8 @@ library Actions { Action memory action = Actions.Action({ chainId: repayInput.chainId, quarkAccount: repayInput.repayer, - actionType: ACTION_TYPE_REPAY, - actionContext: abi.encode(repayActionContext), + actionType: ACTION_TYPE_MORPHO_REPAY, + actionContext: abi.encode(morphoRepayActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), // Null address for OFFCHAIN payment. paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token : address(0), @@ -1213,8 +1209,8 @@ library Actions { return ds[0]; } - function emptyRepayActionContext() external pure returns (RepayActionContext memory) { - RepayActionContext[] memory rs = new RepayActionContext[](1); + function emptyMorphoRepayActionContext() external pure returns (MorphoRepayActionContext memory) { + MorphoRepayActionContext[] memory rs = new MorphoRepayActionContext[](1); return rs[0]; } diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index b768f3e6..8c2ced2d 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1534,31 +1534,28 @@ contract QuarkBuilder { if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_BORROW)) { continue; + } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_MORPHO_REPAY)) { + Actions.MorphoRepayActionContext memory morphoRepayActionContext = + abi.decode(nonBridgeAction.actionContext, (Actions.MorphoRepayActionContext)); + if (morphoRepayActionContext.amount == type(uint256).max) { + paymentTokenCost += morphoRepayMaxAmount( + chainAccountsList, + morphoRepayActionContext.chainId, + morphoRepayActionContext.token, + morphoRepayActionContext.collateralToken, + account + ); + } else { + paymentTokenCost += morphoRepayActionContext.amount; + } } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_REPAY)) { Actions.RepayActionContext memory repayActionContext = abi.decode(nonBridgeAction.actionContext, (Actions.RepayActionContext)); if (Strings.stringEqIgnoreCase(repayActionContext.assetSymbol, paymentTokenSymbol)) { if (repayActionContext.amount == type(uint256).max) { - uint256 repayAmount; - if (repayActionContext.comet != address(0)) { - // Comet repay - repayAmount = cometRepayMaxAmount( + paymentTokenCost += cometRepayMaxAmount( chainAccountsList, repayActionContext.chainId, repayActionContext.comet, account ); - } else if (repayActionContext.morpho != address(0)) { - // Morpho repay - repayAmount = morphoRepayMaxAmount( - chainAccountsList, - repayActionContext.chainId, - repayActionContext.token, - repayActionContext.collateralTokens[0], - account - ); - } else { - revert InvalidRepayActionContext(); - } - - paymentTokenCost += repayAmount; } else { paymentTokenCost += repayActionContext.amount; } diff --git a/test/builder/QuarkBuilderCometBorrow.t.sol b/test/builder/QuarkBuilderCometBorrow.t.sol index b1cbc2a6..476c5cd4 100644 --- a/test/builder/QuarkBuilderCometBorrow.t.sol +++ b/test/builder/QuarkBuilderCometBorrow.t.sol @@ -178,9 +178,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(1) }) ), "action context encoded from BorrowActionContext" @@ -295,9 +293,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(1) }) ), "action context encoded from BorrowActionContext" @@ -421,9 +417,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(1) }) ), "action context encoded from BorrowActionContext" @@ -546,9 +540,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(1) }) ), "action context encoded from BorrowActionContext" @@ -734,9 +726,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: USDT_PRICE, - token: usdt_(8453), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdt_(8453) }) ), "action context encoded from BorrowActionContext" @@ -922,9 +912,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { collateralAssetSymbols: collateralAssetSymbols, comet: cometUsdc_(1), price: WETH_PRICE, - token: weth_(8453), - morpho: address(0), - morphoMarketId: bytes32(0) + token: weth_(8453) }) ), "action context encoded from BorrowActionContext" diff --git a/test/builder/QuarkBuilderCometRepay.t.sol b/test/builder/QuarkBuilderCometRepay.t.sol index 66ef4524..873df7cf 100644 --- a/test/builder/QuarkBuilderCometRepay.t.sol +++ b/test/builder/QuarkBuilderCometRepay.t.sol @@ -205,9 +205,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(1) }) ), "action context encoded from RepayActionContext" @@ -324,9 +322,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometWeth_(1), price: WETH_PRICE, - token: weth_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: weth_(1) }) ), "action context encoded from RepayActionContext" @@ -451,9 +447,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(1) }) ), "action context encoded from RepayActionContext" @@ -579,9 +573,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometWeth_(1), price: WETH_PRICE, - token: weth_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: weth_(1) }) ), "action context encoded from RepayActionContext" @@ -768,9 +760,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(8453), price: USDC_PRICE, - token: usdc_(8453), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(8453) }) ), "action context encoded from RepayActionContext" @@ -892,9 +882,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(1), price: USDC_PRICE, - token: usdc_(1), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(1) }) ), "action context encoded from RepayActionContext" @@ -1086,9 +1074,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { collateralTokens: collateralTokens, comet: cometUsdc_(8453), price: USDC_PRICE, - token: usdc_(8453), - morpho: address(0), - morphoMarketId: bytes32(0) + token: usdc_(8453) }) ), "action context encoded from RepayActionContext" diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index fcddd31b..dafa6bee 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -132,9 +132,9 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeCall( MorphoActions.supplyCollateralAndBorrow, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) + (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) ), - "calldata is MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xal1ce), address(0xal1ce));" + "calldata is MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xal1ce), address(0xal1ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 1); assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); @@ -146,39 +146,29 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[0].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[0].actionType, "MORPHO_BORROW", "action type is 'MORPHO_BORROW'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 1e8; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = WBTC_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = wbtc_(1); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "WBTC"; - assertEq( result.actions[0].actionContext, abi.encode( - Actions.BorrowActionContext({ + Actions.MorphoBorrowActionContext({ amount: 1e6, assetSymbol: "USDC", chainId: 1, - collateralAmounts: collateralAmounts, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - collateralAssetSymbols: collateralAssetSymbols, - comet: address(0), + collateralAmount: 1e8, + collateralTokenPrice: WBTC_PRICE, + collateralToken: wbtc_(1), + collateralAssetSymbol: "WBTC", price: USDC_PRICE, token: usdc_(1), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(1), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) }) ), - "action context encoded from BorrowActionContext" + "action context encoded from MorphoBorrowActionContext" ); // TODO: Check the contents of the EIP712 data @@ -236,13 +226,13 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ); callDatas[1] = abi.encodeCall( MorphoActions.supplyCollateralAndBorrow, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 1e18, 1e6) + (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 1e18, 1e6) ); assertEq( result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), - "calldata is Multicall.run([wrapperActionsAddress, MorphoActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 10e18), MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 1e6, address(0xa11ce), address(0xa11ce))" + "calldata is Multicall.run([wrapperActionsAddress, MorphoActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 10e18), MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 1e6, address(0xa11ce), address(0xa11ce))" ); assertEq(result.quarkOperations[0].scriptSources.length, 3); assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); @@ -256,37 +246,29 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 8453, "operation is on chainid 8453"); assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[0].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[0].actionType, "MORPHO_BORROW", "action type is 'MORPHO_BORROW'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 1e18; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = WETH_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = weth_(8453); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "WETH"; + assertEq( result.actions[0].actionContext, abi.encode( - Actions.BorrowActionContext({ + Actions.MorphoBorrowActionContext({ amount: 1e6, assetSymbol: "USDC", chainId: 8453, - collateralAmounts: collateralAmounts, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - collateralAssetSymbols: collateralAssetSymbols, - comet: address(0), + collateralAmount: 1e18, + collateralTokenPrice: WETH_PRICE, + collateralToken: weth_(8453), + collateralAssetSymbol: "WETH", price: USDC_PRICE, token: usdc_(8453), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(8453), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "WETH", "USDC")) }) ), - "action context encoded from BorrowActionContext" + "action context encoded from MorphoBorrowActionContext" ); // TODO: Check the contents of the EIP712 data @@ -346,11 +328,11 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { MorphoActionsAddress, abi.encodeCall( MorphoActions.supplyCollateralAndBorrow, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) + (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) ), 0.1e6 ), - "calldata is Paycall.run(MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e8, 1e6, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); @@ -366,39 +348,29 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[0].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[0].actionType, "MORPHO_BORROW", "action type is 'MORPHO_BORROW'"); assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 1e8; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = WBTC_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = wbtc_(1); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "WBTC"; - assertEq( result.actions[0].actionContext, abi.encode( - Actions.BorrowActionContext({ + Actions.MorphoBorrowActionContext({ amount: 1e6, assetSymbol: "USDC", chainId: 1, - collateralAmounts: collateralAmounts, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - collateralAssetSymbols: collateralAssetSymbols, - comet: address(0), + collateralAmount: 1e8, + collateralTokenPrice: WBTC_PRICE, + collateralToken: wbtc_(1), + collateralAssetSymbol: "WBTC", price: USDC_PRICE, token: usdc_(1), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(1), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) }) ), - "action context encoded from BorrowActionContext" + "action context encoded from MorphoBorrowActionContext" ); // TODO: Check the contents of the EIP712 data @@ -458,7 +430,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { MorphoActionsAddress, abi.encodeCall( MorphoActions.supplyCollateralAndBorrow, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) + (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e8, 1e6) ), 0.1e6 ), @@ -478,39 +450,28 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[0].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[0].actionType, "MORPHO_BORROW", "action type is 'MORPHO_MORPHO_BORROW'"); assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); - - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 1e8; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = WBTC_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = wbtc_(1); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "WBTC"; - assertEq( result.actions[0].actionContext, abi.encode( - Actions.BorrowActionContext({ + Actions.MorphoBorrowActionContext({ amount: 1e6, assetSymbol: "USDC", chainId: 1, - collateralAmounts: collateralAmounts, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - collateralAssetSymbols: collateralAssetSymbols, - comet: address(0), + collateralAmount: 1e8, + collateralTokenPrice: WBTC_PRICE, + collateralToken: wbtc_(1), + collateralAssetSymbol: "WBTC", price: USDC_PRICE, token: usdc_(1), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(1), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) }) ), - "action context encoded from BorrowActionContext" + "action context encoded from MorphoBorrowActionContext" ); // TODO: Check the contents of the EIP712 data @@ -607,11 +568,11 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { MorphoActionsAddress, abi.encodeCall( MorphoActions.supplyCollateralAndBorrow, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), 1e18, 0.2e18) + (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), 1e18, 0.2e18) ), 1e6 ), - "calldata is Paycall.run(MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, cbETH, WETH), 1e18, 0.2e18, address(0xa11ce), address(0xa11ce));" + "calldata is Paycall.run(MorphoActions.supplyCollateralAndBorrow(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, cbETH, WETH), 1e18, 0.2e18, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[1].scriptSources.length, 2); assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoActions).creationCode); @@ -651,39 +612,29 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[1].actionType, "BORROW", "action type is 'BORROW'"); + assertEq(result.actions[1].actionType, "MORPHO_BORROW", "action type is 'MORPHO_BORROW'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 1e6, "payment should have max cost of 1e6"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 1e18; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = CBETH_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = cbEth_(8453); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "cbETH"; - assertEq( result.actions[1].actionContext, abi.encode( - Actions.BorrowActionContext({ + Actions.MorphoBorrowActionContext({ amount: 0.2e18, assetSymbol: "WETH", chainId: 8453, - collateralAmounts: collateralAmounts, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - collateralAssetSymbols: collateralAssetSymbols, - comet: address(0), + collateralAmount: 1e18, + collateralTokenPrice: CBETH_PRICE, + collateralToken: cbEth_(8453), + collateralAssetSymbol: "cbETH", price: WETH_PRICE, token: weth_(8453), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(8453), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "cbETH", "WETH")) }) ), - "action context encoded from BorrowActionContext" + "action context encoded from MorphoBorrowActionContext" ); // TODO: Check the contents of the EIP712 data diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 46003e09..f93e1340 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -136,9 +136,9 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, 1e8) + (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, 1e8) ), - "calldata is MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 1e8, address(0xa11ce), address(0xa11ce));" + "calldata is MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 1e8, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 1); assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); @@ -150,39 +150,29 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[0].actionType, "MORPHO_REPAY", "action type is 'MORPHO_REPAY'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 1e8; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = WBTC_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = wbtc_(1); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "WBTC"; - assertEq( result.actions[0].actionContext, abi.encode( - Actions.RepayActionContext({ + Actions.MorphoRepayActionContext({ amount: 1e6, assetSymbol: "USDC", chainId: 1, - collateralAmounts: collateralAmounts, - collateralAssetSymbols: collateralAssetSymbols, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - comet: address(0), + collateralAmount: 1e8, + collateralAssetSymbol: "WBTC", + collateralTokenPrice: WBTC_PRICE, + collateralToken: wbtc_(1), price: USDC_PRICE, token: usdc_(1), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(1), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) }) ), - "action context encoded from RepayActionContext" + "action context encoded from MorphoRepayActionContext" ); // TODO: Check the contents of the EIP712 data @@ -247,13 +237,13 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); callDatas[1] = abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), 1e18, 0, 0e18) + (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), 1e18, 0, 0e18) ); assertEq( result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), - "calldata is Multicall.run([wrapperActionsAddress, MorphoActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 0, 0e18, address(0xa11ce), address(0xa11ce))" + "calldata is Multicall.run([wrapperActionsAddress, MorphoActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 0, 0e18, address(0xa11ce), address(0xa11ce))" ); assertEq(result.quarkOperations[0].scriptSources.length, 3); assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); @@ -267,39 +257,29 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 8453, "operation is on chainid 8453"); assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[0].actionType, "MORPHO_REPAY", "action type is 'MORPHO_REPAY'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 0e18; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = CBETH_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = cbEth_(8453); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "cbETH"; - assertEq( result.actions[0].actionContext, abi.encode( - Actions.RepayActionContext({ + Actions.MorphoRepayActionContext({ amount: 1e18, assetSymbol: "WETH", chainId: 8453, - collateralAmounts: collateralAmounts, - collateralAssetSymbols: collateralAssetSymbols, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - comet: address(0), + collateralAmount: 0, + collateralAssetSymbol: "cbETH", + collateralTokenPrice: CBETH_PRICE, + collateralToken: cbEth_(8453), price: WETH_PRICE, token: weth_(8453), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(8453), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "cbETH", "WETH")) }) ), - "action context encoded from RepayActionContext" + "action context encoded from MorphoRepayActionContext" ); // TODO: Check the contents of the EIP712 data @@ -364,11 +344,11 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { MorphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, 0e8) + (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, 0e8) ), 0.1e6 ), - "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 0);" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 0);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); @@ -384,7 +364,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[0].actionType, "MORPHO_REPAY", "action type is 'MORPHO_REPAY'"); assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); @@ -401,22 +381,21 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.actions[0].actionContext, abi.encode( - Actions.RepayActionContext({ + Actions.MorphoRepayActionContext({ amount: 1e6, assetSymbol: "USDC", chainId: 1, - collateralAmounts: collateralAmounts, - collateralAssetSymbols: collateralAssetSymbols, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - comet: address(0), + collateralAmount: 0, + collateralAssetSymbol: "WBTC", + collateralTokenPrice: WBTC_PRICE, + collateralToken: wbtc_(1), price: USDC_PRICE, token: usdc_(1), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(1), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) }) ), - "action context encoded from RepayActionContext" + "action context encoded from MorphoRepayActionContext" ); // TODO: Check the contents of the EIP712 data @@ -520,11 +499,11 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { MorphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, - (MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 2e6, 0, 0e18) + (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 2e6, 0, 0e18) ), 0.2e6 ), - "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), 2e6, 0, 0);" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, WETH, USDC), 2e6, 0, 0);" ); assertEq(result.quarkOperations[1].scriptSources.length, 2); assertEq(result.quarkOperations[1].scriptSources[0], type(MorphoActions).creationCode); @@ -564,39 +543,29 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[1].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[1].actionType, "MORPHO_REPAY", "action type is 'MORPHO_REPAY'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.2e6, "payment should have max cost of 0.2e6"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 0e18; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = WETH_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = weth_(8453); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "WETH"; - assertEq( result.actions[1].actionContext, abi.encode( - Actions.RepayActionContext({ + Actions.MorphoRepayActionContext({ amount: 2e6, assetSymbol: "USDC", chainId: 8453, - collateralAmounts: collateralAmounts, - collateralAssetSymbols: collateralAssetSymbols, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - comet: address(0), + collateralAmount: 0, + collateralAssetSymbol: "WETH", + collateralTokenPrice: WETH_PRICE, + collateralToken: weth_(8453), price: USDC_PRICE, token: usdc_(8453), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(8453), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "WETH", "USDC")) }) ), - "action context encoded from RepayActionContext" + "action context encoded from MorphoRepayActionContext" ); // TODO: Check the contents of the EIP712 data @@ -665,7 +634,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, ( - MorphoInfo.getMorphoAddress(), + MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), type(uint256).max, 0, @@ -674,7 +643,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), 0.1e6 ), - "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(1, WBTC, USDC), type(uint256).max, 0, 0);" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), type(uint256).max, 0, 0);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); @@ -691,39 +660,29 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[0].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[0].actionType, "MORPHO_REPAY", "action type is 'MORPHO_REPAY'"); assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 0e8; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = WBTC_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = wbtc_(1); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "WBTC"; - assertEq( result.actions[0].actionContext, abi.encode( - Actions.RepayActionContext({ + Actions.MorphoRepayActionContext({ amount: type(uint256).max, assetSymbol: "USDC", chainId: 1, - collateralAmounts: collateralAmounts, - collateralAssetSymbols: collateralAssetSymbols, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - comet: address(0), + collateralAmount: 0, + collateralAssetSymbol: "WBTC", + collateralTokenPrice: WBTC_PRICE, + collateralToken: wbtc_(1), price: USDC_PRICE, token: usdc_(1), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(1), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(1, "WBTC", "USDC")) }) ), - "action context encoded from RepayActionContext" + "action context encoded from MorphoRepayActionContext" ); // TODO: Check the contents of the EIP712 data @@ -839,7 +798,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, ( - MorphoInfo.getMorphoAddress(), + MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), type(uint256).max, 0, @@ -848,7 +807,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), 0.1e6 ), - "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(), MorphoInfo.getMarketParams(8453, WETH, USDC), type(uint256).max, 0, 0);" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, WETH, USDC), type(uint256).max, 0, 0);" ); assertEq(result.quarkOperations[1].scriptSources.length, 2); @@ -889,39 +848,29 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); - assertEq(result.actions[1].actionType, "REPAY", "action type is 'REPAY'"); + assertEq(result.actions[1].actionType, "MORPHO_REPAY", "action type is 'MORPHO_REPAY'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 0e18; - uint256[] memory collateralTokenPrices = new uint256[](1); - collateralTokenPrices[0] = WETH_PRICE; - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = weth_(8453); - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "WETH"; - assertEq( result.actions[1].actionContext, abi.encode( - Actions.RepayActionContext({ + Actions.MorphoRepayActionContext({ amount: type(uint256).max, assetSymbol: "USDC", chainId: 8453, - collateralAmounts: collateralAmounts, - collateralAssetSymbols: collateralAssetSymbols, - collateralTokenPrices: collateralTokenPrices, - collateralTokens: collateralTokens, - comet: address(0), + collateralAmount: 0, + collateralAssetSymbol: "WETH", + collateralTokenPrice: WETH_PRICE, + collateralToken: weth_(8453), price: USDC_PRICE, token: usdc_(8453), - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(8453), morphoMarketId: MorphoInfo.marketId(MorphoInfo.getMarketParams(8453, "WETH", "USDC")) }) ), - "action context encoded from RepayActionContext" + "action context encoded from MorphoRepayActionContext" ); // TODO: Check the contents of the EIP712 data diff --git a/test/builder/lib/QuarkBuilderTest.sol b/test/builder/lib/QuarkBuilderTest.sol index 07a37a28..d482937c 100644 --- a/test/builder/lib/QuarkBuilderTest.sol +++ b/test/builder/lib/QuarkBuilderTest.sol @@ -406,7 +406,7 @@ contract QuarkBuilderTest { morphoPositions[i] = Accounts.MorphoPositions({ marketId: morphoPortfolio.marketId, - morpho: MorphoInfo.getMorphoAddress(), + morpho: MorphoInfo.getMorphoAddress(chainId), loanToken: loanAsset, collateralToken: collateralAsset, borrowPosition: Accounts.MorphoBorrowPosition({ diff --git a/test/on_chain_info_verificaion/MorphoInfo.t.sol b/test/on_chain_info_verificaion/MorphoInfo.t.sol index b10d52ce..05e8b95b 100644 --- a/test/on_chain_info_verificaion/MorphoInfo.t.sol +++ b/test/on_chain_info_verificaion/MorphoInfo.t.sol @@ -81,7 +81,7 @@ contract MorphoInfoTest is Test { if (marketKeys[i].chainId == chainId) { MarketParams memory marketParams = MorphoInfo.getMarketParams(markets, marketKeys[i]); (uint128 totalSupplyAssets,,,, uint128 lastUpdate,) = - IMorpho(MorphoInfo.getMorphoAddress()).market(MorphoInfo.marketId(marketParams)); + IMorpho(MorphoInfo.getMorphoAddress(chainId)).market(MorphoInfo.marketId(marketParams)); assertGt( totalSupplyAssets, 0, From 3bdeba9e49c751d4fa690d3f4a0c6f29ca2782cf Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 07:58:26 -0700 Subject: [PATCH 33/50] handle morpho action types --- src/builder/QuarkBuilder.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index 8c2ced2d..e35fa71a 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1534,6 +1534,8 @@ contract QuarkBuilder { if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_BORROW)) { continue; + } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_MORPHO_BORROW)) { + continue; } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_MORPHO_REPAY)) { Actions.MorphoRepayActionContext memory morphoRepayActionContext = abi.decode(nonBridgeAction.actionContext, (Actions.MorphoRepayActionContext)); From f8781a890b8a365154537940ad89271d2d7c5c59 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Mon, 2 Sep 2024 08:01:32 -0700 Subject: [PATCH 34/50] add test net to morphoinfo and intent --- src/builder/MorphoInfo.sol | 2 +- src/builder/QuarkBuilder.sol | 26 ++++++++++----------- test/builder/QuarkBuilderMorphoBorrow.t.sol | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol index 8b3e938c..90321e31 100644 --- a/src/builder/MorphoInfo.sol +++ b/src/builder/MorphoInfo.sol @@ -11,7 +11,7 @@ library MorphoInfo { // Note: Current Morpho has same address across mainnet and base function getMorphoAddress(uint256 chainId) internal pure returns (address) { - if (chainId == 1 || chainId == 8453) { + if (chainId == 1 || chainId == 8453 || chainId == 11155111 || chainId == 84532) { return 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; } else { revert UnsupportedChainId(); diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index e35fa71a..b45eafec 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1539,25 +1539,25 @@ contract QuarkBuilder { } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_MORPHO_REPAY)) { Actions.MorphoRepayActionContext memory morphoRepayActionContext = abi.decode(nonBridgeAction.actionContext, (Actions.MorphoRepayActionContext)); - if (morphoRepayActionContext.amount == type(uint256).max) { - paymentTokenCost += morphoRepayMaxAmount( - chainAccountsList, - morphoRepayActionContext.chainId, - morphoRepayActionContext.token, - morphoRepayActionContext.collateralToken, - account - ); - } else { - paymentTokenCost += morphoRepayActionContext.amount; - } + if (morphoRepayActionContext.amount == type(uint256).max) { + paymentTokenCost += morphoRepayMaxAmount( + chainAccountsList, + morphoRepayActionContext.chainId, + morphoRepayActionContext.token, + morphoRepayActionContext.collateralToken, + account + ); + } else { + paymentTokenCost += morphoRepayActionContext.amount; + } } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_REPAY)) { Actions.RepayActionContext memory repayActionContext = abi.decode(nonBridgeAction.actionContext, (Actions.RepayActionContext)); if (Strings.stringEqIgnoreCase(repayActionContext.assetSymbol, paymentTokenSymbol)) { if (repayActionContext.amount == type(uint256).max) { paymentTokenCost += cometRepayMaxAmount( - chainAccountsList, repayActionContext.chainId, repayActionContext.comet, account - ); + chainAccountsList, repayActionContext.chainId, repayActionContext.comet, account + ); } else { paymentTokenCost += repayActionContext.amount; } diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index dafa6bee..646136f9 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -362,7 +362,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainId: 1, collateralAmount: 1e8, collateralTokenPrice: WBTC_PRICE, - collateralToken: wbtc_(1), + collateralToken: wbtc_(1), collateralAssetSymbol: "WBTC", price: USDC_PRICE, token: usdc_(1), From cd590a31492cbf884a0cdd96eb0d9d55c08846f7 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Tue, 3 Sep 2024 17:24:51 -0700 Subject: [PATCH 35/50] fix typo Potential > Potentially --- src/builder/MorphoInfo.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol index 90321e31..9932e21c 100644 --- a/src/builder/MorphoInfo.sol +++ b/src/builder/MorphoInfo.sol @@ -319,7 +319,7 @@ library MorphoInfo { } // Morpho vaults - // Note: Potential can add other key (i.e. curator) for supporting multiple vaults with same assets + // Note: Potentially can add other key (i.e. curator) for supporting multiple vaults with same assets struct MorphoVaultKey { uint256 chainId; string supplyAssetSymbol; From 4dc55a20c7510f5ce2807ece6043dcd7c3fc7bb5 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Tue, 3 Sep 2024 17:25:08 -0700 Subject: [PATCH 36/50] add NOTE on renaming generic actions to comet's action --- src/builder/Actions.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 4f7f99d1..ce927819 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -25,11 +25,13 @@ import {List} from "./List.sol"; library Actions { /* ===== Constants ===== */ + // TODO: Rename ACTION_TYPE_BORROW to ACTION_TYPE_COMET_BORROW, as now we have more than one borrow market string constant ACTION_TYPE_BORROW = "BORROW"; string constant ACTION_TYPE_MORPHO_BORROW = "MORPHO_BORROW"; string constant ACTION_TYPE_BRIDGE = "BRIDGE"; string constant ACTION_TYPE_CLAIM_REWARDS = "CLAIM_REWARDS"; string constant ACTION_TYPE_DRIP_TOKENS = "DRIP_TOKENS"; + // TODO: Rename ACTION_TYPE_REPAY to ACTION_TYPE_COMET_REPAY, as now we have more than one borrow market string constant ACTION_TYPE_REPAY = "REPAY"; string constant ACTION_TYPE_MORPHO_REPAY = "MORPHO_REPAY"; string constant ACTION_TYPE_SUPPLY = "SUPPLY"; From 642cc168aff408db99b3f49314bf2678d6caf28b Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Tue, 3 Sep 2024 18:47:22 -0700 Subject: [PATCH 37/50] as no more shares tracking, borrowedBalacnes became borrowed just like Comet's position --- src/builder/Accounts.sol | 6 +++--- test/builder/lib/QuarkBuilderTest.sol | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/builder/Accounts.sol b/src/builder/Accounts.sol index 773f6eae..56307da2 100644 --- a/src/builder/Accounts.sol +++ b/src/builder/Accounts.sol @@ -69,12 +69,12 @@ library Accounts { struct MorphoBorrowPosition { address[] accounts; - uint256[] borrowedBalances; + uint256[] borrowed; } struct MorphoCollateralPosition { address[] accounts; - uint256[] collateralBalances; + uint256[] balances; } function findChainAccounts(uint256 chainId, ChainAccounts[] memory chainAccountsList) @@ -302,7 +302,7 @@ library Accounts { findMorphoPositions(chainId, loanToken, collateralToken, chainAccountsList); for (uint256 i = 0; i < morphoPositions.borrowPosition.accounts.length; ++i) { if (morphoPositions.borrowPosition.accounts[i] == account) { - totalBorrow = morphoPositions.borrowPosition.borrowedBalances[i]; + totalBorrow = morphoPositions.borrowPosition.borrowed[i]; } } } diff --git a/test/builder/lib/QuarkBuilderTest.sol b/test/builder/lib/QuarkBuilderTest.sol index d482937c..235af9de 100644 --- a/test/builder/lib/QuarkBuilderTest.sol +++ b/test/builder/lib/QuarkBuilderTest.sol @@ -411,11 +411,11 @@ contract QuarkBuilderTest { collateralToken: collateralAsset, borrowPosition: Accounts.MorphoBorrowPosition({ accounts: Arrays.addressArray(account), - borrowedBalances: Arrays.uintArray(morphoPortfolio.borrowedBalance) + borrowed: Arrays.uintArray(morphoPortfolio.borrowedBalance) }), collateralPosition: Accounts.MorphoCollateralPosition({ accounts: Arrays.addressArray(account), - collateralBalances: Arrays.uintArray(morphoPortfolio.collateralBalance) + balances: Arrays.uintArray(morphoPortfolio.collateralBalance) }) }); } From 8a48c7d335ebec3b232e03e3de1c282ea2ef3264 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 17:49:31 -0700 Subject: [PATCH 38/50] replace address(0) to PaymentInfo.NON_TOKEN_PAYEMNT --- src/builder/Actions.sol | 51 ++++++++++++---------- src/builder/PaymentInfo.sol | 1 + test/builder/QuarkBuilderMorphoRepay.t.sol | 18 ++++---- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index ce927819..98056454 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -25,13 +25,13 @@ import {List} from "./List.sol"; library Actions { /* ===== Constants ===== */ - // TODO: Rename ACTION_TYPE_BORROW to ACTION_TYPE_COMET_BORROW, as now we have more than one borrow market + // TODO: (LHT-86) Rename ACTION_TYPE_BORROW to ACTION_TYPE_COMET_BORROW, as now we have more than one borrow market string constant ACTION_TYPE_BORROW = "BORROW"; string constant ACTION_TYPE_MORPHO_BORROW = "MORPHO_BORROW"; string constant ACTION_TYPE_BRIDGE = "BRIDGE"; string constant ACTION_TYPE_CLAIM_REWARDS = "CLAIM_REWARDS"; string constant ACTION_TYPE_DRIP_TOKENS = "DRIP_TOKENS"; - // TODO: Rename ACTION_TYPE_REPAY to ACTION_TYPE_COMET_REPAY, as now we have more than one borrow market + // TODO: (LHT-86) Rename ACTION_TYPE_REPAY to ACTION_TYPE_COMET_REPAY, as now we have more than one borrow market string constant ACTION_TYPE_REPAY = "REPAY"; string constant ACTION_TYPE_MORPHO_REPAY = "MORPHO_REPAY"; string constant ACTION_TYPE_SUPPLY = "SUPPLY"; @@ -541,8 +541,9 @@ library Actions { actionType: ACTION_TYPE_BRIDGE, actionContext: abi.encode(bridgeActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, useQuotecall), - // Null address for OFFCHAIN payment. - paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, bridge.srcChainId).token : address(0), + paymentToken: payment.isToken + ? PaymentInfo.knownToken(payment.currency, bridge.srcChainId).token + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, bridge.srcChainId) : 0 }); @@ -614,8 +615,9 @@ library Actions { actionType: ACTION_TYPE_BORROW, actionContext: abi.encode(borrowActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), - // Null address for OFFCHAIN payment. - paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : address(0), + paymentToken: payment.isToken + ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0 }); @@ -687,8 +689,9 @@ library Actions { actionType: ACTION_TYPE_REPAY, actionContext: abi.encode(repayActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), - // Null address for OFFCHAIN payment. - paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token : address(0), + paymentToken: payment.isToken + ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0 }); @@ -744,8 +747,9 @@ library Actions { actionType: ACTION_TYPE_SUPPLY, actionContext: abi.encode(cometSupplyActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), - // Null address for OFFCHAIN payment. - paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, cometSupply.chainId).token : address(0), + paymentToken: payment.isToken + ? PaymentInfo.knownToken(payment.currency, cometSupply.chainId).token + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometSupply.chainId) : 0 }); @@ -802,10 +806,9 @@ library Actions { actionType: ACTION_TYPE_WITHDRAW, actionContext: abi.encode(cometWithdrawActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), - // Null address for OFFCHAIN payment. paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, cometWithdraw.chainId).token - : address(0), + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometWithdraw.chainId) : 0 }); @@ -866,8 +869,9 @@ library Actions { actionType: ACTION_TYPE_TRANSFER, actionContext: abi.encode(transferActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, useQuotecall), - // Null address for OFFCHAIN payment. - paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, transfer.chainId).token : address(0), + paymentToken: payment.isToken + ? PaymentInfo.knownToken(payment.currency, transfer.chainId).token + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, transfer.chainId) : 0 }); @@ -932,8 +936,9 @@ library Actions { actionType: ACTION_TYPE_MORPHO_BORROW, actionContext: abi.encode(borrowActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), - // Null address for OFFCHAIN payment. - paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : address(0), + paymentToken: payment.isToken + ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0 }); @@ -965,7 +970,6 @@ library Actions { MorphoInfo.getMorphoAddress(repayInput.chainId), MorphoInfo.getMarketParams(repayInput.chainId, repayInput.collateralAssetSymbol, repayInput.assetSymbol), repayInput.amount, - 0, repayInput.collateralAmount ); @@ -1000,8 +1004,9 @@ library Actions { actionType: ACTION_TYPE_MORPHO_REPAY, actionContext: abi.encode(morphoRepayActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, false), - // Null address for OFFCHAIN payment. - paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token : address(0), + paymentToken: payment.isToken + ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0 }); @@ -1052,10 +1057,9 @@ library Actions { : ACTION_TYPE_WRAP, actionContext: abi.encode(wrapOrUnwrapActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, useQuotecall), - // Null address for OFFCHAIN payment. paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, wrapOrUnwrap.chainId).token - : address(0), + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, wrapOrUnwrap.chainId) : 0 }); @@ -1127,8 +1131,9 @@ library Actions { actionType: ACTION_TYPE_SWAP, actionContext: abi.encode(swapActionContext), paymentMethod: PaymentInfo.paymentMethodForPayment(payment, useQuotecall), - // Null address for OFFCHAIN payment. - paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, swap.chainId).token : address(0), + paymentToken: payment.isToken + ? PaymentInfo.knownToken(payment.currency, swap.chainId).token + : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0 }); diff --git a/src/builder/PaymentInfo.sol b/src/builder/PaymentInfo.sol index 36b24125..c5448c75 100644 --- a/src/builder/PaymentInfo.sol +++ b/src/builder/PaymentInfo.sol @@ -7,6 +7,7 @@ library PaymentInfo { string constant PAYMENT_METHOD_OFFCHAIN = "OFFCHAIN"; string constant PAYMENT_METHOD_PAYCALL = "PAY_CALL"; string constant PAYMENT_METHOD_QUOTECALL = "QUOTE_CALL"; + address constant NON_TOKEN_PAYMENT = address(0); error NoKnownPaymentToken(uint256 chainId); error MaxCostMissingForChain(uint256 chainId); diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index f93e1340..6c31a006 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -136,9 +136,9 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, - (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, 1e8) + (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 1e8) ), - "calldata is MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 1e8, address(0xa11ce), address(0xa11ce));" + "calldata is MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 1e8, address(0xa11ce), address(0xa11ce));" ); assertEq(result.quarkOperations[0].scriptSources.length, 1); assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); @@ -237,7 +237,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); callDatas[1] = abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, - (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), 1e18, 0, 0e18) + (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "cbETH", "WETH"), 1e18, 0e18) ); assertEq( @@ -344,11 +344,11 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { MorphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, - (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0, 0e8) + (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0e8) ), 0.1e6 ), - "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0, 0);" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), 1e6, 0);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(MorphoActions).creationCode); @@ -499,7 +499,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { MorphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, - (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 2e6, 0, 0e18) + (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 2e6, 0e18) ), 0.2e6 ), @@ -637,13 +637,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), type(uint256).max, - 0, 0 ) ), 0.1e6 ), - "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), type(uint256).max, 0, 0);" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, WBTC, USDC), type(uint256).max, 0);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); @@ -801,13 +800,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), type(uint256).max, - 0, 0 ) // Repaying in shares ), 0.1e6 ), - "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, WETH, USDC), type(uint256).max, 0, 0);" + "calldata is Paycall.run(MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, WETH, USDC), type(uint256).max, 0);" ); assertEq(result.quarkOperations[1].scriptSources.length, 2); From 2a83d5168ba485884172af4749709e609b7bf3da Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 18:12:49 -0700 Subject: [PATCH 39/50] add empty action context MorphoBorrow and MorphoRepay, and added back the mistakenly removed empty context --- src/builder/Actions.sol | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 98056454..d0b78114 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -1201,6 +1201,11 @@ library Actions { return bs[0]; } + function emptyMorphoBorrowActionContext() external pure returns (MorphoBorrowActionContext memory) { + MorphoBorrowActionContext[] memory mb = new MorphoBorrowActionContext[](1); + return mb[0]; + } + function emptyBridgeActionContext() external pure returns (BridgeActionContext memory) { BridgeActionContext[] memory bs = new BridgeActionContext[](1); return bs[0]; @@ -1216,16 +1221,21 @@ library Actions { return ds[0]; } - function emptyMorphoRepayActionContext() external pure returns (MorphoRepayActionContext memory) { - MorphoRepayActionContext[] memory rs = new MorphoRepayActionContext[](1); + function emptyRepayActionContext() external pure returns (RepayActionContext memory) { + RepayActionContext[] memory rs = new RepayActionContext[](1); return rs[0]; } + function emptyMorphoRepayActionContext() external pure returns (MorphoRepayActionContext memory) { + MorphoRepayActionContext[] memory mr = new MorphoRepayActionContext[](1); + return mr[0]; + } + function emptySupplyActionContext() external pure returns (SupplyActionContext memory) { SupplyActionContext[] memory ss = new SupplyActionContext[](1); return ss[0]; } - + function emptySwapActionContext() external pure returns (SwapActionContext memory) { SwapActionContext[] memory ss = new SwapActionContext[](1); return ss[0]; From 685531b8b614f563667a8c31d4fcd52e8cc56616 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 18:36:50 -0700 Subject: [PATCH 40/50] use return instead of named --- src/builder/QuarkBuilder.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index b45eafec..36f26710 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -82,11 +82,11 @@ contract QuarkBuilder { address loanToken, address collateralToken, address repayer - ) internal pure returns (uint256 amount) { + ) internal pure returns (uint256) { uint256 totalBorrowForAccount = Accounts.totalMorphoBorrowForAccount(chainAccountsList, chainId, loanToken, collateralToken, repayer); uint256 buffer = totalBorrowForAccount / 1000; // 0.1% - amount = totalBorrowForAccount + buffer; + return totalBorrowForAccount + buffer; } function cometRepay( From 8a5630fbd0e0d2db1172be75e379a9c69fc80674 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 19:34:09 -0700 Subject: [PATCH 41/50] don't pre-declare as there is no loop to check --- src/builder/Actions.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index d0b78114..c76f9faf 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -1235,7 +1235,7 @@ library Actions { SupplyActionContext[] memory ss = new SupplyActionContext[](1); return ss[0]; } - + function emptySwapActionContext() external pure returns (SwapActionContext memory) { SwapActionContext[] memory ss = new SwapActionContext[](1); return ss[0]; From 208767da182b0bbca42d8f3de277bde8d99b2233 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 19:35:04 -0700 Subject: [PATCH 42/50] don't pre-declare as no loop to check, forgot to check in this file as well --- src/builder/QuarkBuilder.sol | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index 36f26710..316e5fa4 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1016,7 +1016,8 @@ contract QuarkBuilder { List.DynamicArray memory quarkOperations = List.newList(); bool useQuotecall = false; // never use Quotecall - bool paymentTokenIsCollateralAsset = false; + bool paymentTokenIsCollateralAsset = + Strings.stringEqIgnoreCase(borrowIntent.collateralAssetSymbol, payment.currency); assertFundsAvailable( borrowIntent.chainId, @@ -1026,10 +1027,6 @@ contract QuarkBuilder { payment ); - if (Strings.stringEqIgnoreCase(borrowIntent.collateralAssetSymbol, payment.currency)) { - paymentTokenIsCollateralAsset = true; - } - if ( needsBridgedFunds( borrowIntent.collateralAssetSymbol, @@ -1551,15 +1548,15 @@ contract QuarkBuilder { paymentTokenCost += morphoRepayActionContext.amount; } } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_REPAY)) { - Actions.RepayActionContext memory repayActionContext = + Actions.RepayActionContext memory cometRepayActionContext = abi.decode(nonBridgeAction.actionContext, (Actions.RepayActionContext)); - if (Strings.stringEqIgnoreCase(repayActionContext.assetSymbol, paymentTokenSymbol)) { - if (repayActionContext.amount == type(uint256).max) { + if (Strings.stringEqIgnoreCase(cometRepayActionContext.assetSymbol, paymentTokenSymbol)) { + if (cometRepayActionContext.amount == type(uint256).max) { paymentTokenCost += cometRepayMaxAmount( - chainAccountsList, repayActionContext.chainId, repayActionContext.comet, account + chainAccountsList, cometRepayActionContext.chainId, cometRepayActionContext.comet, account ); } else { - paymentTokenCost += repayActionContext.amount; + paymentTokenCost += cometRepayActionContext.amount; } } } else if (Strings.stringEqIgnoreCase(nonBridgeAction.actionType, Actions.ACTION_TYPE_SUPPLY)) { From dd2ea0f2affa7c8afff7a88da026d5ee9bd29cad Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Thu, 5 Sep 2024 19:45:57 -0700 Subject: [PATCH 43/50] rename tests, and use helper to get script address --- test/builder/QuarkBuilderMorphoBorrow.t.sol | 22 +++------------------ test/builder/QuarkBuilderMorphoRepay.t.sol | 4 ++-- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index 646136f9..f60e5769 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -103,29 +103,13 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { QuarkBuilder.BuilderResult memory result = builder.morphoBorrow( borrowIntent_(1, "USDC", 1e6, "WBTC", 1e8), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsd_() ); - - assertEq(result.paymentCurrency, "usd", "usd currency"); - + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations + assertEq(result.paymentCurrency, "usd", "usd currency"); assertEq(result.quarkOperations.length, 1, "one operation"); assertEq( result.quarkOperations[0].scriptAddress, - address( - uint160( - uint256( - keccak256( - abi.encodePacked( - bytes1(0xff), - /* codeJar address */ - address(CodeJarHelper.CODE_JAR_ADDRESS), - uint256(0), - /* script bytecode */ - keccak256(type(MorphoActions).creationCode) - ) - ) - ) - ) - ), + MorphoActionsAddress, "script address is correct given the code jar address on mainnet" ); assertEq( diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 6c31a006..0f0065b9 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -181,7 +181,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); } - function testCometRepayWithAutoWrapper() public { + function testMorphoRepayWithAutoWrapper() public { ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); chainPortfolios[0] = ChainPortfolio({ chainId: 1, @@ -288,7 +288,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); } - function testCometRepayWithPaycall() public { + function testMorphoRepayWithPaycall() public { ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); chainPortfolios[0] = ChainPortfolio({ chainId: 1, From 707a240da2e478993e1ff7fc4fc6871b186e7394 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Fri, 6 Sep 2024 03:03:12 -0700 Subject: [PATCH 44/50] update repay to also ateempt to bridge payment token --- src/builder/QuarkBuilder.sol | 32 ++++++++++++++++++++- test/builder/QuarkBuilderMorphoBorrow.t.sol | 25 ++++++++++++++++ test/builder/QuarkBuilderMorphoRepay.t.sol | 2 +- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index 316e5fa4..d6d94763 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1183,7 +1183,7 @@ contract QuarkBuilder { ) external pure returns (BuilderResult memory) { bool isMaxRepay = repayIntent.amount == type(uint256).max; bool useQuotecall = false; // never use Quotecall - + bool paymentTokenIsRepaymentlAsset = Strings.stringEqIgnoreCase(repayIntent.assetSymbol, payment.currency); // Only use repayAmount for purpose of bridging, will still use uint256 max for MorphoScript uint256 repayAmount = repayIntent.amount; if (isMaxRepay) { @@ -1231,6 +1231,36 @@ contract QuarkBuilder { } } + // Only bridge payment token if it is not the repayment asset (which the above briding action didn't cover) + if (payment.isToken && !paymentTokenIsRepaymentlAsset) { + uint256 maxCostOnDstChain = PaymentInfo.findMaxCost(payment, repayIntent.chainId); + if (Strings.stringEqIgnoreCase(payment.currency, repayIntent.assetSymbol)) { + maxCostOnDstChain = Math.subtractFlooredAtZero(maxCostOnDstChain, repayIntent.amount); + } + + if (needsBridgedFunds(payment.currency, maxCostOnDstChain, repayIntent.chainId, chainAccountsList, payment)) + { + (IQuarkWallet.QuarkOperation[] memory bridgeQuarkOperations, Actions.Action[] memory bridgeActions) = + Actions.constructBridgeOperations( + Actions.BridgeOperationInfo({ + assetSymbol: payment.currency, + amountNeededOnDst: maxCostOnDstChain, + dstChainId: repayIntent.chainId, + recipient: repayIntent.repayer, + blockTimestamp: repayIntent.blockTimestamp, + useQuotecall: useQuotecall + }), + chainAccountsList, + payment + ); + + for (uint256 i = 0; i < bridgeQuarkOperations.length; ++i) { + List.addQuarkOperation(quarkOperations, bridgeQuarkOperations[i]); + List.addAction(actions, bridgeActions[i]); + } + } + } + // Auto-wrap checkAndInsertWrapOrUnwrapAction( actions, diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index f60e5769..62a44fe7 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -626,4 +626,29 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); } + + function testMorphoBorrowMaxCostTooHighForBridgePaymentToken() public { + QuarkBuilder builder = new QuarkBuilder(); + PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); + maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.5e6}); // action costs .5 USDC + + ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](1); + chainPortfolios[0] = ChainPortfolio({ + chainId: 1, + account: address(0xa11ce), + nextNonce: 12, + assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), + assetBalances: Arrays.uintArray(0.4e6, 0, 2e8, 1e18), // user does not have enough USDC + cometPortfolios: emptyCometPortfolios_(), + morphoPortfolios: emptyMorphoPortfolios_() + }); + + vm.expectRevert(abi.encodeWithSelector(Actions.NotEnoughFundsToBridge.selector, "usdc", 0.1e6, 0.1e6)); + + builder.morphoBorrow( + borrowIntent_(1, "WETH", 1e18, "WBTC", 0), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsdc_(maxCosts) + ); + } } diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 0f0065b9..5bbe8167 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -65,7 +65,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { morphoPortfolios: emptyMorphoPortfolios_() }); - vm.expectRevert(QuarkBuilder.MaxCostTooHigh.selector); + vm.expectRevert(abi.encodeWithSelector(Actions.NotEnoughFundsToBridge.selector, "usdc", 0.1e6, 0.1e6)); builder.morphoRepay( repayIntent_(8453, "WETH", 1e18, "cbETH", 1e18), From d91604ed5136b7f692cdb3d35333573eaf51b97a Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Fri, 6 Sep 2024 10:58:30 -0700 Subject: [PATCH 45/50] drop TODOs --- test/builder/QuarkBuilderMorphoBorrow.t.sol | 10 ------- test/builder/QuarkBuilderMorphoRepay.t.sol | 31 ++------------------- 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index 62a44fe7..d07c60eb 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -154,8 +154,6 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -254,8 +252,6 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -356,8 +352,6 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -457,8 +451,6 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -620,8 +612,6 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 5bbe8167..c83fe700 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -109,27 +109,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); assertEq(result.paymentCurrency, "usd", "usd currency"); - + address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one operation"); assertEq( result.quarkOperations[0].scriptAddress, - address( - uint160( - uint256( - keccak256( - abi.encodePacked( - bytes1(0xff), - /* codeJar address */ - address(CodeJarHelper.CODE_JAR_ADDRESS), - uint256(0), - /* script bytecode */ - keccak256(type(MorphoActions).creationCode) - ) - ) - ) - ) - ), + MorphoActionsAddress, "script address is correct given the code jar address on mainnet" ); assertEq( @@ -174,8 +159,6 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -281,8 +264,6 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -397,8 +378,6 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -567,8 +546,6 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -683,8 +660,6 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -870,8 +845,6 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); - - // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); From cad44ff63ce29371d8b0dcf89c4c41c6aab0a419 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 11 Sep 2024 16:18:13 -0700 Subject: [PATCH 46/50] RPC endpoint update --- test/on_chain_info_verificaion/MorphoInfo.t.sol | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/test/on_chain_info_verificaion/MorphoInfo.t.sol b/test/on_chain_info_verificaion/MorphoInfo.t.sol index 05e8b95b..9027d013 100644 --- a/test/on_chain_info_verificaion/MorphoInfo.t.sol +++ b/test/on_chain_info_verificaion/MorphoInfo.t.sol @@ -19,9 +19,7 @@ contract MorphoInfoTest is Test { function testEthMainnet() public { // Fork setup to get on-chain on eth mainnet vm.createSelectFork( - string.concat( - "https://node-provider.compound.finance/ethereum-mainnet/", vm.envString("NODE_PROVIDER_BYPASS_KEY") - ), + vm.envString("MAINNET_RPC_URL"), 20580267 // 2024-08-21 16:27:00 PST ); @@ -32,9 +30,7 @@ contract MorphoInfoTest is Test { function testBaseMainnet() public { // Fork setup to get on-chain on base mainnet vm.createSelectFork( - string.concat( - "https://node-provider.compound.finance/base-mainnet/", vm.envString("NODE_PROVIDER_BYPASS_KEY") - ), + vm.envString("MAINNET_RPC_URL"), 18746757 // 2024-08-21 16:27:00 PST ); @@ -45,9 +41,7 @@ contract MorphoInfoTest is Test { function testEthSepolia() public { // Fork setup to get on-chain on base sepolia vm.createSelectFork( - string.concat( - "https://node-provider.compound.finance/ethereum-sepolia/", vm.envString("NODE_PROVIDER_BYPASS_KEY") - ), + vm.envString("MAINNET_RPC_URL"), 6546096 // 2024-08-21 16:27:00 PST ); @@ -58,9 +52,7 @@ contract MorphoInfoTest is Test { function testBaseSepolia() public { // Fork setup to get on-chain on base sepolia vm.createSelectFork( - string.concat( - "https://node-provider.compound.finance/base-sepolia/", vm.envString("NODE_PROVIDER_BYPASS_KEY") - ), + vm.envString("MAINNET_RPC_URL"), 14257289 // 2024-08-21 16:27:00 PST ); From f176c4493b2cf58acc6c7b22b386049dab4846db Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 11 Sep 2024 16:40:59 -0700 Subject: [PATCH 47/50] add new rpc url --- test/on_chain_info_verificaion/MorphoInfo.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/on_chain_info_verificaion/MorphoInfo.t.sol b/test/on_chain_info_verificaion/MorphoInfo.t.sol index 9027d013..ded392ba 100644 --- a/test/on_chain_info_verificaion/MorphoInfo.t.sol +++ b/test/on_chain_info_verificaion/MorphoInfo.t.sol @@ -30,7 +30,7 @@ contract MorphoInfoTest is Test { function testBaseMainnet() public { // Fork setup to get on-chain on base mainnet vm.createSelectFork( - vm.envString("MAINNET_RPC_URL"), + vm.envString("BASE_MAINNET_RPC_URL"), 18746757 // 2024-08-21 16:27:00 PST ); @@ -41,7 +41,7 @@ contract MorphoInfoTest is Test { function testEthSepolia() public { // Fork setup to get on-chain on base sepolia vm.createSelectFork( - vm.envString("MAINNET_RPC_URL"), + vm.envString("SEPOLIA_RPC_URL"), 6546096 // 2024-08-21 16:27:00 PST ); @@ -52,7 +52,7 @@ contract MorphoInfoTest is Test { function testBaseSepolia() public { // Fork setup to get on-chain on base sepolia vm.createSelectFork( - vm.envString("MAINNET_RPC_URL"), + vm.envString("BASE_SEPOLIA_RPC_URL"), 14257289 // 2024-08-21 16:27:00 PST ); From e5b32641a4cb436c264a30d8361b1cdb33539994 Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 11 Sep 2024 16:51:28 -0700 Subject: [PATCH 48/50] rename folder to - instead of _ --- .../MorphoInfo.t.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{on_chain_info_verificaion => on-chain-info-verificaion}/MorphoInfo.t.sol (100%) diff --git a/test/on_chain_info_verificaion/MorphoInfo.t.sol b/test/on-chain-info-verificaion/MorphoInfo.t.sol similarity index 100% rename from test/on_chain_info_verificaion/MorphoInfo.t.sol rename to test/on-chain-info-verificaion/MorphoInfo.t.sol From 9ff003f1bba0fdc920e69932ab2263cb6fdb1c3c Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 11 Sep 2024 16:51:54 -0700 Subject: [PATCH 49/50] fix typo in folder name --- .../MorphoInfo.t.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{on-chain-info-verificaion => on-chain-info-verification}/MorphoInfo.t.sol (100%) diff --git a/test/on-chain-info-verificaion/MorphoInfo.t.sol b/test/on-chain-info-verification/MorphoInfo.t.sol similarity index 100% rename from test/on-chain-info-verificaion/MorphoInfo.t.sol rename to test/on-chain-info-verification/MorphoInfo.t.sol From 44b92973bbf9569d6774c3230884d591db80de4e Mon Sep 17 00:00:00 2001 From: Hans Wang Date: Wed, 11 Sep 2024 17:18:11 -0700 Subject: [PATCH 50/50] include other rpc endpoints --- .github/workflows/gas-snapshot.yml | 3 +++ .github/workflows/test.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/gas-snapshot.yml b/.github/workflows/gas-snapshot.yml index 8a2dad84..d27946d1 100644 --- a/.github/workflows/gas-snapshot.yml +++ b/.github/workflows/gas-snapshot.yml @@ -34,6 +34,9 @@ jobs: | tee .gas-snapshot.new env: MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} + BASE_MAINNET_RPC_URL: ${{ secrets.BASE_MAINNET_RPC_URL }} + SEPOLIA_RPC_URL: ${{ secrets.SEPOLIA_RPC_URL }} + BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }} - name: Check diff tolerance run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 93efced0..d57dffa4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,3 +38,6 @@ jobs: id: test env: MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} + BASE_MAINNET_RPC_URL: ${{ secrets.BASE_MAINNET_RPC_URL }} + SEPOLIA_RPC_URL: ${{ secrets.SEPOLIA_RPC_URL }} + BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }}