diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index fa8aaf8..77f37da 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -666,6 +666,83 @@ contract QuarkBuilder { }); } + struct CometWithdrawAndBorrowIntent { + uint256 amount; + string assetSymbol; + uint256 blockTimestamp; + address borrower; + uint256 chainId; + uint256[] collateralAmounts; + string[] collateralAssetSymbols; + address comet; + } + + // Withdraws all assets from the market and borrows the specified amount + function cometWithdrawAndBorrow( + CometWithdrawAndBorrowIntent memory withdrawAndBorrowIntent, + Accounts.ChainAccounts[] memory chainAccountsList, + PaymentInfo.Payment memory payment + ) external view returns (BuilderResult memory) { + List.DynamicArray memory actions = List.newList(); + List.DynamicArray memory quarkOperations = List.newList(); + + BuilderResult memory withdrawActions = this.cometWithdraw( + CometWithdrawIntent({ + amount: type(uint256).max, + assetSymbol: withdrawAndBorrowIntent.assetSymbol, + blockTimestamp: withdrawAndBorrowIntent.blockTimestamp, + chainId: withdrawAndBorrowIntent.chainId, + comet: withdrawAndBorrowIntent.comet, + withdrawer: withdrawAndBorrowIntent.borrower + }), + chainAccountsList, + payment + ); + + for (uint256 i = 0; i < withdrawActions.quarkOperations.length; ++i) { + List.addQuarkOperation(quarkOperations, withdrawActions.quarkOperations[i]); + } + for (uint256 i = 0; i < withdrawActions.actions.length; ++i) { + List.addAction(actions, withdrawActions.actions[i]); + } + + BuilderResult memory borrowActions = this.cometBorrow( + CometBorrowIntent({ + amount: withdrawAndBorrowIntent.amount, + assetSymbol: withdrawAndBorrowIntent.assetSymbol, + blockTimestamp: withdrawAndBorrowIntent.blockTimestamp, + chainId: withdrawAndBorrowIntent.chainId, + collateralAmounts: withdrawAndBorrowIntent.collateralAmounts, + collateralAssetSymbols: withdrawAndBorrowIntent.collateralAssetSymbols, + comet: withdrawAndBorrowIntent.comet, + borrower: withdrawAndBorrowIntent.borrower + }), + chainAccountsList, + payment + ); + + for (uint256 i = 0; i < borrowActions.quarkOperations.length; ++i) { + List.addQuarkOperation(quarkOperations, borrowActions.quarkOperations[i]); + } + for (uint256 i = 0; i < borrowActions.actions.length; ++i) { + List.addAction(actions, borrowActions.actions[i]); + } + + Actions.Action[] memory actionsArray = List.toActionArray(actions); + IQuarkWallet.QuarkOperation[] memory quarkOperationsArray = List.toQuarkOperationArray(quarkOperations); + + (quarkOperationsArray, actionsArray) = + QuarkOperationHelper.mergeSameChainOperations(quarkOperationsArray, actionsArray); + + return BuilderResult({ + version: VERSION, + actions: actionsArray, + quarkOperations: quarkOperationsArray, + paymentCurrency: payment.currency, + eip712Data: EIP712Helper.eip712DataForQuarkOperations(quarkOperationsArray, actionsArray) + }); + } + struct TransferIntent { uint256 chainId; string assetSymbol; diff --git a/test/builder/QuarkBuilderCometWithdrawAndBorrow.t.sol b/test/builder/QuarkBuilderCometWithdrawAndBorrow.t.sol new file mode 100644 index 0000000..7f87c03 --- /dev/null +++ b/test/builder/QuarkBuilderCometWithdrawAndBorrow.t.sol @@ -0,0 +1,126 @@ +// 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 {Accounts, PaymentInfo, QuarkBuilder, QuarkBuilderTest} from "test/builder/lib/QuarkBuilderTest.sol"; +import {CometWithdrawActions, CometSupplyMultipleAssetsAndBorrow} from "src/DeFiScripts.sol"; +import {Multicall} from "src/Multicall.sol"; +import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; + +contract QuarkBuilderCometWithdrawAndBorrowTest is Test, QuarkBuilderTest { + function cometWithdrawAndBorrow_( + uint256 chainId, + address comet, + string memory assetSymbol, + uint256 amount, + uint256[] memory collateralAmounts, + string[] memory collateralAssetSymbols + ) internal pure returns (QuarkBuilder.CometWithdrawAndBorrowIntent memory) { + return QuarkBuilder.CometWithdrawAndBorrowIntent({ + amount: amount, + assetSymbol: assetSymbol, + blockTimestamp: BLOCK_TIMESTAMP, + borrower: address(0xa11ce), + chainId: chainId, + collateralAmounts: collateralAmounts, + collateralAssetSymbols: collateralAssetSymbols, + comet: comet + }); + } + + function testCometWithdrawAndBorrow() 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(0, 0, 10e18, 0), // user has 10 LINK + cometPortfolios: emptyCometPortfolios_(), + morphoPortfolios: emptyMorphoPortfolios_(), + morphoVaultPortfolios: emptyMorphoVaultPortfolios_() + }); + 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_(), + morphoPortfolios: emptyMorphoPortfolios_(), + morphoVaultPortfolios: emptyMorphoVaultPortfolios_() + }); + + QuarkBuilder builder = new QuarkBuilder(); + QuarkBuilder.BuilderResult memory result = builder.cometWithdrawAndBorrow( + cometWithdrawAndBorrow_(1, cometUsdc_(1), "USDC", 1e18, collateralAmounts, collateralAssetSymbols), + chainAccountsFromChainPortfolios(chainPortfolios), + paymentUsd_() + ); + + address withdrawScriptAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + /* codeJar address */ + address(CodeJarHelper.CODE_JAR_ADDRESS), + uint256(0), + /* script bytecode */ + keccak256(type(CometWithdrawActions).creationCode) + ) + ) + ) + ) + ); + + address borrowScriptAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + /* codeJar address */ + address(CodeJarHelper.CODE_JAR_ADDRESS), + uint256(0), + /* script bytecode */ + keccak256(type(CometSupplyMultipleAssetsAndBorrow).creationCode) + ) + ) + ) + ) + ); + + address[] memory multicallAddresses = new address[](2); + multicallAddresses[0] = withdrawScriptAddress; + multicallAddresses[1] = borrowScriptAddress; + + bytes[] memory multicallCalldata = new bytes[](2); + multicallCalldata[0] = + abi.encodeWithSelector(CometWithdrawActions.withdraw.selector, cometUsdc_(1), usdc_(1), type(uint256).max); + multicallCalldata[1] = abi.encodeWithSelector( + CometSupplyMultipleAssetsAndBorrow.run.selector, + cometUsdc_(1), + Arrays.addressArray(link_(1)), + Arrays.uintArray(1e18), + usdc_(1), + 1e18 + ); + + assertEq( + result.quarkOperations[0].scriptCalldata, + abi.encodeCall(Multicall.run, (multicallAddresses, multicallCalldata)), + "calldata is Multicall.run(withdraw, borrow)" + ); + } +}