diff --git a/.gas-snapshot b/.gas-snapshot index 4545dad2..6eecb369 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,116 +1,120 @@ -ApproveAndSwapTest:testSwap() (gas: 285492) -ApproveAndSwapTest:testSwapFailsIfWeExpectedTooMuch() (gas: 364765) -ApproveAndSwapTest:testSwapFailsWithNoApproval() (gas: 122254) -CCTPBridge:testBridgeToBase() (gas: 146874) -CometClaimRewardsTest:testClaimComp() (gas: 131265) -CometRepayAndWithdrawMultipleAssetsTest:testInvalidInput() (gas: 68180) -CometRepayAndWithdrawMultipleAssetsTest:testRepayAndWithdrawMultipleAssets() (gas: 153860) -CometSupplyMultipleAssetsAndBorrowTest:testInvalidInput() (gas: 68123) -CometSupplyMultipleAssetsAndBorrowTest:testSupplyMultipleAssetsAndBorrow() (gas: 295260) -ConditionalMulticallTest:testConditionalRunEmptyInputIsValid() (gas: 51386) -ConditionalMulticallTest:testConditionalRunInvalidInput() (gas: 68840) -ConditionalMulticallTest:testConditionalRunMulticallError() (gas: 316436) -ConditionalMulticallTest:testConditionalRunOnPeriodicRepay() (gas: 340313) -ConditionalMulticallTest:testConditionalRunPassed() (gas: 284615) -ConditionalMulticallTest:testConditionalRunUnmet() (gas: 103718) -EthcallTest:testEthcallCallReraiseError() (gas: 76316) -EthcallTest:testEthcallCounter() (gas: 69662) -EthcallTest:testEthcallShouldReturnCallResult() (gas: 52545) -EthcallTest:testEthcallSupplyUSDCToComet() (gas: 171965) -EthcallTest:testEthcallWithdrawUSDCFromComet() (gas: 296115) -GetDripTest:testDrip() (gas: 120591) -MorphoActionsTest:testRepayAndWithdrawCollateral() (gas: 130575) -MorphoActionsTest:testRepayMaxAndWithdrawCollateral() (gas: 118293) -MorphoActionsTest:testSupplyCollateralAndBorrow() (gas: 250037) -MorphoRewardsActionsTest:testClaim() (gas: 112124) -MorphoRewardsActionsTest:testClaimAll() (gas: 186069) -MorphoVaultActionsTest:testDeposit() (gas: 762206) -MorphoVaultActionsTest:testWithdraw() (gas: 570979) -MulticallTest:testCallcodeToMulticallSucceedsWhenUninitialized() (gas: 83316) -MulticallTest:testCreateSubWalletAndExecute() (gas: 619226) -MulticallTest:testEmptyInputIsValid() (gas: 50892) -MulticallTest:testExecutorCanMulticallAcrossSubwallets() (gas: 303309) -MulticallTest:testInvalidInput() (gas: 68358) -MulticallTest:testInvokeCounterTwice() (gas: 83470) -MulticallTest:testMulticallError() (gas: 309667) -MulticallTest:testMulticallShouldReturnCallResults() (gas: 86409) -MulticallTest:testRevertsForInvalidCallContext() (gas: 11574) -MulticallTest:testSupplyWETHWithdrawUSDCOnComet() (gas: 259628) -PaycallTest:testInitializeProperlyFromConstructor() (gas: 6430) -PaycallTest:testPaycallForPayWithUSDT() (gas: 125307) -PaycallTest:testPaycallForPayWithWBTC() (gas: 119793) -PaycallTest:testPaycallRevertsWhenCallReverts() (gas: 72350) -PaycallTest:testReturnCallResult() (gas: 92209) -PaycallTest:testRevertWhenCostIsMoreThanMaxPaymentCost() (gas: 119383) -PaycallTest:testRevertsForInvalidCallContext() (gas: 16562) -PaycallTest:testSimpleCounterAndPayWithUSDC() (gas: 148804) -PaycallTest:testSimpleTransferTokenAndPayWithUSDC() (gas: 144444) -PaycallTest:testSupplyWETHWithdrawUSDCOnCometAndPayWithUSDC() (gas: 305017) -PaycallWrapperTest:testSimpleTransferAndWrapForPaycall() (gas: 1342091) -QuotecallTest:testInitializeProperlyFromConstructor() (gas: 7036) -QuotecallTest:testQuotecallForPayWithUSDT() (gas: 125408) -QuotecallTest:testQuotecallForPayWithWBTC() (gas: 119962) -QuotecallTest:testQuotecallRevertsWhenCallReverts() (gas: 108579) -QuotecallTest:testReturnCallResult() (gas: 112319) -QuotecallTest:testRevertsForInvalidCallContext() (gas: 16537) -QuotecallTest:testRevertsWhenQuoteTooHigh() (gas: 155229) -QuotecallTest:testRevertsWhenQuoteTooLow() (gas: 155071) -QuotecallTest:testSimpleCounterAndPayWithUSDC() (gas: 148952) -QuotecallTest:testSimpleTransferTokenAndPayWithUSDC() (gas: 144596) -QuotecallWrapperTest:testSimpleTransferAndWrapForQuotecall() (gas: 1407584) -RecurringSwapTest:testCancelRecurringSwap() (gas: 293567) -RecurringSwapTest:testRecurringSwapCanSwapMultipleTimes() (gas: 425136) -RecurringSwapTest:testRecurringSwapExactInAlternateSwap() (gas: 229791) -RecurringSwapTest:testRecurringSwapExactInSwap() (gas: 237508) -RecurringSwapTest:testRecurringSwapExactOutAlternateSwap() (gas: 233113) -RecurringSwapTest:testRecurringSwapExactOutSwap() (gas: 239457) -RecurringSwapTest:testRecurringSwapWithDifferentCalldata() (gas: 724141) -RecurringSwapTest:testRecurringSwapWithMultiplePriceFeeds() (gas: 254663) -RecurringSwapTest:testRevertsForExpiredQuarkOperation() (gas: 12817) -RecurringSwapTest:testRevertsForInvalidInput() (gas: 143492) -RecurringSwapTest:testRevertsForSwapBeforeNextSwapWindow() (gas: 307929) +ApproveAndSwapTest:testSwap() (gas: 273935) +ApproveAndSwapTest:testSwapFailsIfWeExpectedTooMuch() (gas: 335013) +ApproveAndSwapTest:testSwapFailsWithNoApproval() (gas: 92109) +CCTPBridge:testBridgeToBase() (gas: 128036) +CometClaimRewardsTest:testClaimComp() (gas: 127404) +CometRepayAndWithdrawMultipleAssetsTest:testInvalidInput() (gas: 44602) +CometRepayAndWithdrawMultipleAssetsTest:testRepayAndWithdrawMultipleAssets() (gas: 134584) +CometSupplyMultipleAssetsAndBorrowTest:testInvalidInput() (gas: 44546) +CometSupplyMultipleAssetsAndBorrowTest:testSupplyMultipleAssetsAndBorrow() (gas: 276105) +ConditionalMulticallTest:testConditionalRunEmptyInputIsValid() (gas: 44953) +ConditionalMulticallTest:testConditionalRunInvalidInput() (gas: 45053) +ConditionalMulticallTest:testConditionalRunMulticallError() (gas: 288790) +ConditionalMulticallTest:testConditionalRunOnPeriodicRepay() (gas: 276093) +ConditionalMulticallTest:testConditionalRunPassed() (gas: 266956) +ConditionalMulticallTest:testConditionalRunUnmet() (gas: 76859) +EthcallTest:testEthcallCallReraiseError() (gas: 53333) +EthcallTest:testEthcallCounter() (gas: 65125) +EthcallTest:testEthcallShouldReturnCallResult() (gas: 45940) +EthcallTest:testEthcallSupplyUSDCToComet() (gas: 152864) +EthcallTest:testEthcallWithdrawUSDCFromComet() (gas: 305482) +GetDripTest:testDrip() (gas: 117212) +MorphoActionsTest:testRepayAndWithdrawCollateral() (gas: 124949) +MorphoActionsTest:testRepayMaxAndWithdrawCollateral() (gas: 99344) +MorphoActionsTest:testSupplyCollateralAndBorrow() (gas: 246334) +MorphoInfoTest:testBaseMainnet() (gas: 1622233) +MorphoInfoTest:testBaseSepolia() (gas: 743646) +MorphoInfoTest:testEthMainnet() (gas: 2636638) +MorphoInfoTest:testEthSepolia() (gas: 644206) +MorphoRewardsActionsTest:testClaim() (gas: 107567) +MorphoRewardsActionsTest:testClaimAll() (gas: 179400) +MorphoVaultActionsTest:testDeposit() (gas: 758763) +MorphoVaultActionsTest:testWithdraw() (gas: 567620) +MulticallTest:testCallcodeToMulticallSucceedsWhenUninitialized() (gas: 73035) +MulticallTest:testCreateSubWalletAndExecute() (gas: 602078) +MulticallTest:testEmptyInputIsValid() (gas: 44599) +MulticallTest:testExecutorCanMulticallAcrossSubwallets() (gas: 251487) +MulticallTest:testInvalidInput() (gas: 44813) +MulticallTest:testInvokeCounterTwice() (gas: 73189) +MulticallTest:testMulticallError() (gas: 281688) +MulticallTest:testMulticallShouldReturnCallResults() (gas: 75670) +MulticallTest:testRevertsForInvalidCallContext() (gas: 11543) +MulticallTest:testSupplyWETHWithdrawUSDCOnComet() (gas: 246618) +PaycallTest:testInitializeProperlyFromConstructor() (gas: 6412) +PaycallTest:testPaycallAddress() (gas: 204118) +PaycallTest:testPaycallForPayWithUSDT() (gas: 115625) +PaycallTest:testPaycallForPayWithWBTC() (gas: 108765) +PaycallTest:testPaycallRevertsWhenCallReverts() (gas: 49555) +PaycallTest:testReturnCallResult() (gas: 85461) +PaycallTest:testRevertWhenCostIsMoreThanMaxPaymentCost() (gas: 95221) +PaycallTest:testRevertsForInvalidCallContext() (gas: 15789) +PaycallTest:testSimpleCounterAndPayWithUSDC() (gas: 136629) +PaycallTest:testSimpleTransferTokenAndPayWithUSDC() (gas: 137984) +PaycallTest:testSupplyWETHWithdrawUSDCOnCometAndPayWithUSDC() (gas: 289774) +PaycallWrapperTest:testSimpleTransferAndWrapForPaycall() (gas: 4289135) +QuotecallTest:testInitializeProperlyFromConstructor() (gas: 6996) +QuotecallTest:testQuotecallForPayWithUSDT() (gas: 115729) +QuotecallTest:testQuotecallForPayWithWBTC() (gas: 108950) +QuotecallTest:testQuotecallRevertsWhenCallReverts() (gas: 85838) +QuotecallTest:testReturnCallResult() (gas: 105548) +QuotecallTest:testRevertsForInvalidCallContext() (gas: 15764) +QuotecallTest:testRevertsWhenQuoteTooHigh() (gas: 131301) +QuotecallTest:testRevertsWhenQuoteTooLow() (gas: 131143) +QuotecallTest:testSimpleCounterAndPayWithUSDC() (gas: 136765) +QuotecallTest:testSimpleTransferTokenAndPayWithUSDC() (gas: 138132) +QuotecallWrapperTest:testSimpleTransferAndWrapForQuotecall() (gas: 4347080) +RecurringSwapTest:testCancelRecurringSwap() (gas: 272600) +RecurringSwapTest:testRecurringSwapCanSwapMultipleTimes() (gas: 337021) +RecurringSwapTest:testRecurringSwapExactInAlternateSwap() (gas: 216061) +RecurringSwapTest:testRecurringSwapExactInSwap() (gas: 223781) +RecurringSwapTest:testRecurringSwapExactOutAlternateSwap() (gas: 219405) +RecurringSwapTest:testRecurringSwapExactOutSwap() (gas: 225744) +RecurringSwapTest:testRecurringSwapWithDifferentCalldata() (gas: 570083) +RecurringSwapTest:testRecurringSwapWithMultiplePriceFeeds() (gas: 240880) +RecurringSwapTest:testRevertsForExpiredQuarkOperation() (gas: 9578) +RecurringSwapTest:testRevertsForInvalidInput() (gas: 91043) +RecurringSwapTest:testRevertsForSwapBeforeNextSwapWindow() (gas: 245871) RecurringSwapTest:testRevertsForSwapBeforeStartTime() (gas: 9223372036854754743) -RecurringSwapTest:testRevertsWhenSlippageParamsConfiguredWrong() (gas: 260596) -RecurringSwapTest:testRevertsWhenSlippageTooHigh() (gas: 260796) -SupplyActionsTest:testInvalidInput() (gas: 67616) -SupplyActionsTest:testRepayBorrow() (gas: 89628) -SupplyActionsTest:testSupply() (gas: 131927) -SupplyActionsTest:testSupplyFrom() (gas: 111824) -SupplyActionsTest:testSupplyMultipleCollateral() (gas: 270579) -SupplyActionsTest:testSupplyTo() (gas: 131476) -TransferActionsTest:testRevertsForTransferERC777ReentrancyAttackWithReentrancyGuard() (gas: 148745) -TransferActionsTest:testRevertsForTransferReentrancyAttackWithReentrancyGuard() (gas: 130322) -TransferActionsTest:testRevertsForTransferReentrancyAttackWithoutCallbackEnabled() (gas: 99351) -TransferActionsTest:testRevertsForTransferReentrantAttackWithStolenSignature() (gas: 110234) -TransferActionsTest:testTransferERC20TokenToEOA() (gas: 70280) -TransferActionsTest:testTransferERC20TokenToQuarkWallet() (gas: 71604) -TransferActionsTest:testTransferERC777SuccessWithEvilReceiverWithoutAttackAttempt() (gas: 106630) -TransferActionsTest:testTransferERC777TokenReentrancyAttackSuccessWithCallbackEnabled() (gas: 144402) -TransferActionsTest:testTransferNativeTokenToEOA() (gas: 79394) -TransferActionsTest:testTransferNativeTokenToQuarkWallet() (gas: 54956) -TransferActionsTest:testTransferReentrancyAttackSuccessWithCallbackEnabled() (gas: 128482) -TransferActionsTest:testTransferSuccessWithEvilReceiverWithoutAttackAttempt() (gas: 87222) -UniswapFlashLoanTest:testFlashLoanForCollateralSwapOnCompound() (gas: 426291) -UniswapFlashLoanTest:testRevertsForInsufficientFundsToRepayFlashLoan() (gas: 192671) -UniswapFlashLoanTest:testRevertsForInvalidCaller() (gas: 70165) -UniswapFlashLoanTest:testRevertsIfCalledDirectly() (gas: 10598) -UniswapFlashLoanTest:testTokensOrderInvariant() (gas: 94563) -UniswapFlashSwapExactOutTest:testInvalidCallerFlashSwap() (gas: 70209) -UniswapFlashSwapExactOutTest:testNotEnoughToPayFlashSwap() (gas: 293339) -UniswapFlashSwapExactOutTest:testRevertsIfCalledDirectly() (gas: 10724) -UniswapFlashSwapExactOutTest:testUniswapFlashSwapExactOutLeverageComet() (gas: 353254) -UniswapSwapActionsTest:testApprovalRefund() (gas: 163410) -UniswapSwapActionsTest:testBuyAssetOneStop() (gas: 250917) -UniswapSwapActionsTest:testBuyAssetTwoStops() (gas: 357575) -UniswapSwapActionsTest:testSellAssetOneStop() (gas: 248255) -UniswapSwapActionsTest:testSellAssetTwoStops() (gas: 361500) -WithdrawActionsTest:testBorrow() (gas: 153735) -WithdrawActionsTest:testInvalidInput() (gas: 67488) -WithdrawActionsTest:testWithdraw() (gas: 83594) -WithdrawActionsTest:testWithdrawFrom() (gas: 83108) -WithdrawActionsTest:testWithdrawMultipleAssets() (gas: 159195) -WithdrawActionsTest:testWithdrawTo() (gas: 83573) -WrapperScriptsTest:testUnwrapWETH() (gas: 57751) -WrapperScriptsTest:testUnwrapWstETH() (gas: 100520) -WrapperScriptsTest:testWrapETH() (gas: 76834) -WrapperScriptsTest:testWrapStETH() (gas: 124640) \ No newline at end of file +RecurringSwapTest:testRevertsWhenSlippageParamsConfiguredWrong() (gas: 227765) +RecurringSwapTest:testRevertsWhenSlippageTooHigh() (gas: 227969) +SupplyActionsTest:testInvalidInput() (gas: 44611) +SupplyActionsTest:testRepayBorrow() (gas: 70904) +SupplyActionsTest:testSupply() (gas: 113213) +SupplyActionsTest:testSupplyFrom() (gas: 93016) +SupplyActionsTest:testSupplyMultipleCollateral() (gas: 251264) +SupplyActionsTest:testSupplyTo() (gas: 112708) +TransferActionsTest:testRevertsForTransferERC777ReentrancyAttackWithReentrancyGuard() (gas: 98027) +TransferActionsTest:testRevertsForTransferReentrancyAttackWithReentrancyGuard() (gas: 78814) +TransferActionsTest:testRevertsForTransferReentrancyAttackWithoutCallbackEnabled() (gas: 73817) +TransferActionsTest:testRevertsForTransferReentrantAttackWithStolenSignature() (gas: 84625) +TransferActionsTest:testTransferERC20TokenToEOA() (gas: 51656) +TransferActionsTest:testTransferERC20TokenToQuarkWallet() (gas: 52976) +TransferActionsTest:testTransferERC777SuccessWithEvilReceiverWithoutAttackAttempt() (gas: 74483) +TransferActionsTest:testTransferERC777TokenReentrancyAttackSuccessWithCallbackEnabled() (gas: 110337) +TransferActionsTest:testTransferNativeTokenToEOA() (gas: 75996) +TransferActionsTest:testTransferNativeTokenToQuarkWallet() (gas: 50662) +TransferActionsTest:testTransferReentrancyAttackSuccessWithCallbackEnabled() (gas: 93738) +TransferActionsTest:testTransferSuccessWithEvilReceiverWithoutAttackAttempt() (gas: 54853) +UniswapFlashLoanTest:testFlashLoanForCollateralSwapOnCompound() (gas: 368458) +UniswapFlashLoanTest:testRevertsForInsufficientFundsToRepayFlashLoan() (gas: 138730) +UniswapFlashLoanTest:testRevertsForInvalidCaller() (gas: 46273) +UniswapFlashLoanTest:testTokensOrderInvariant() (gas: 81719) +UniswapFlashSwapExactOutTest:testInvalidCallerFlashSwap() (gas: 46469) +UniswapFlashSwapExactOutTest:testNotEnoughToPayFlashSwap() (gas: 233929) +UniswapFlashSwapExactOutTest:testRevertsIfCalledDirectly() (gas: 104305) +UniswapFlashSwapExactOutTest:testUniswapFlashSwapExactOutLeverageComet() (gas: 311072) +UniswapSwapActionsTest:testApprovalRefund() (gas: 159188) +UniswapSwapActionsTest:testBuyAssetOneStop() (gas: 250010) +UniswapSwapActionsTest:testBuyAssetTwoStops() (gas: 363122) +UniswapSwapActionsTest:testSellAssetOneStop() (gas: 246694) +UniswapSwapActionsTest:testSellAssetTwoStops() (gas: 368042) +WithdrawActionsTest:testBorrow() (gas: 150239) +WithdrawActionsTest:testInvalidInput() (gas: 44591) +WithdrawActionsTest:testWithdraw() (gas: 64886) +WithdrawActionsTest:testWithdrawFrom() (gas: 64296) +WithdrawActionsTest:testWithdrawMultipleAssets() (gas: 139993) +WithdrawActionsTest:testWithdrawTo() (gas: 64824) +WrapperScriptsTest:testUnwrapWETH() (gas: 39141) +WrapperScriptsTest:testUnwrapWstETH() (gas: 87724) +WrapperScriptsTest:testWrapETH() (gas: 73485) +WrapperScriptsTest:testWrapStETH() (gas: 105947) \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d57dffa4..c988c5e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: "nightly-de33b6af53005037b463318d2628b5cfcaf39916" + version: nightly - name: Install dependencies run: forge install diff --git a/foundry.toml b/foundry.toml index 253a0299..3313f6e7 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,8 +2,8 @@ src = "src" out = "out" test = "test" -solc = "0.8.23" -evm_version = "paris" +solc = "0.8.27" +evm_version = "cancun" bytecode_hash = "none" cbor_metadata = false diff --git a/lib/quark b/lib/quark index ab18773a..4d89e844 160000 --- a/lib/quark +++ b/lib/quark @@ -1 +1 @@ -Subproject commit ab18773ae7876e42bcfaa8b8d432db55bd43151f +Subproject commit 4d89e844dfcb8c4767b1f4830e1b9ac9c219a23e diff --git a/script/DeployQuarkScripts.s.sol b/script/DeployQuarkScripts.s.sol index 1b9cb5a1..7adc8d28 100644 --- a/script/DeployQuarkScripts.s.sol +++ b/script/DeployQuarkScripts.s.sol @@ -1,19 +1,14 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Script.sol"; import "forge-std/console.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; -import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; - import {Ethcall} from "quark-core-scripts/src/Ethcall.sol"; import {Multicall} from "quark-core-scripts/src/Multicall.sol"; -import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; - import { CometSupplyActions, CometWithdrawActions, diff --git a/src/BridgeScripts.sol b/src/BridgeScripts.sol index cd223c08..905cb811 100644 --- a/src/BridgeScripts.sol +++ b/src/BridgeScripts.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {ITokenMessenger} from "./interfaces/ITokenMessenger.sol"; diff --git a/src/Cancel.sol b/src/Cancel.sol new file mode 100644 index 00000000..c37f653b --- /dev/null +++ b/src/Cancel.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.27; + +import {IQuarkWallet} from "quark-core/src/QuarkWallet.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; + +/** + * @title Cancel Core Script + * @notice Core transaction script that can be used to cancel quark operations. + * @author Legend Labs, Inc. + */ +contract Cancel { + /** + * @notice May cancel a script by being run as a no-op (no operation). + */ + function nop() external pure {} + + /** + * @notice Cancels a script by calling into nonce manager to cancel the script's nonce. + * @param nonce The nonce of the quark operation to cancel (exhaust) + */ + function cancel(bytes32 nonce) external { + nonceManager().cancel(nonce); + } + + /** + * @notice Cancels many scripts by calling into nonce manager to cancel each script's nonce. + * @param nonces A list of nonces of the quark operations to cancel (exhaust) + */ + function cancelMany(bytes32[] calldata nonces) external { + QuarkNonceManager manager = nonceManager(); + for (uint256 i = 0; i < nonces.length; ++i) { + bytes32 nonce = nonces[i]; + manager.cancel(nonce); + } + } + + function nonceManager() internal view returns (QuarkNonceManager) { + return QuarkNonceManager(IQuarkWallet(address(this)).nonceManager()); + } +} diff --git a/src/ConditionalMulticall.sol b/src/ConditionalMulticall.sol index 1a71edd4..13c9c64b 100644 --- a/src/ConditionalMulticall.sol +++ b/src/ConditionalMulticall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "./lib/ConditionalChecker.sol"; diff --git a/src/DeFiScripts.sol b/src/DeFiScripts.sol index 35ade4ab..1c0e48b1 100644 --- a/src/DeFiScripts.sol +++ b/src/DeFiScripts.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; diff --git a/src/Ethcall.sol b/src/Ethcall.sol index 77402537..017e7a68 100644 --- a/src/Ethcall.sol +++ b/src/Ethcall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; /** * @title Ethcall Core Script diff --git a/src/GetDrip.sol b/src/GetDrip.sol index 934ce184..83a76b1f 100644 --- a/src/GetDrip.sol +++ b/src/GetDrip.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.23; +pragma solidity 0.8.27; // To handle loading up new accounts on stage and dev interface Fauceteer { diff --git a/src/MorphoScripts.sol b/src/MorphoScripts.sol index 017a4eda..39719c5b 100644 --- a/src/MorphoScripts.sol +++ b/src/MorphoScripts.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; diff --git a/src/Multicall.sol b/src/Multicall.sol index b7759381..479ef7ae 100644 --- a/src/Multicall.sol +++ b/src/Multicall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; /** * @title Multicall Core Script diff --git a/src/Paycall.sol b/src/Paycall.sol index 7bd15f32..44f7b975 100644 --- a/src/Paycall.sol +++ b/src/Paycall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "./vendor/chainlink/AggregatorV3Interface.sol"; import "openzeppelin/token/ERC20/utils/SafeERC20.sol"; diff --git a/src/Quotecall.sol b/src/Quotecall.sol index e8151710..8173414f 100644 --- a/src/Quotecall.sol +++ b/src/Quotecall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "./vendor/chainlink/AggregatorV3Interface.sol"; import "openzeppelin/token/ERC20/utils/SafeERC20.sol"; diff --git a/src/RecurringSwap.sol b/src/RecurringSwap.sol index f67a3e11..cc02f2c0 100644 --- a/src/RecurringSwap.sol +++ b/src/RecurringSwap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; @@ -41,7 +41,7 @@ contract RecurringSwap is QuarkScript { uint256 public constant PRECISION_FACTOR = 1e18; /** - * @dev Note: This script uses the following storage layout in the QuarkStateManager: + * @dev Note: This script uses the following storage layout in the QuarkNonceManager: * mapping(bytes32 hashedSwapConfig => uint256 nextSwapTime) * where hashedSwapConfig = keccak256(SwapConfig) */ @@ -80,18 +80,11 @@ contract RecurringSwap is QuarkScript { bool[] shouldInvert; } - /// @notice Cancel the recurring swap for the current nonce - function cancel() external { - // Not explicitly clearing the nonce just cancels the replayable txn - } - /** * @notice Execute a swap given a configuration for a recurring swap * @param config The configuration for a recurring swap order */ function swap(SwapConfig calldata config) public { - allowReplay(); - if (config.slippageParams.priceFeeds.length == 0) { revert InvalidInput(); } diff --git a/src/UniswapFlashLoan.sol b/src/UniswapFlashLoan.sol index 016652ae..e8324e0a 100644 --- a/src/UniswapFlashLoan.sol +++ b/src/UniswapFlashLoan.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "openzeppelin/token/ERC20/utils/SafeERC20.sol"; import "v3-core/contracts/interfaces/IUniswapV3Pool.sol"; diff --git a/src/UniswapFlashSwapExactOut.sol b/src/UniswapFlashSwapExactOut.sol index 266ac349..2e8f500b 100644 --- a/src/UniswapFlashSwapExactOut.sol +++ b/src/UniswapFlashSwapExactOut.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "openzeppelin/token/ERC20/utils/SafeERC20.sol"; import "v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; diff --git a/src/WrapperScripts.sol b/src/WrapperScripts.sol index 9123fab5..bf5f8882 100644 --- a/src/WrapperScripts.sol +++ b/src/WrapperScripts.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {IWETH} from "./interfaces/IWETH.sol"; diff --git a/src/builder/Accounts.sol b/src/builder/Accounts.sol index 02396656..a98c0b12 100644 --- a/src/builder/Accounts.sol +++ b/src/builder/Accounts.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {Math} from "src/lib/Math.sol"; import {PaymentInfo} from "./PaymentInfo.sol"; @@ -8,21 +8,20 @@ import {PaymentInfo} from "./PaymentInfo.sol"; import {TokenWrapper} from "./TokenWrapper.sol"; library Accounts { + error QuarkSecretNotFound(address account); + struct ChainAccounts { uint256 chainId; - QuarkState[] quarkStates; + QuarkSecret[] quarkSecrets; AssetPositions[] assetPositionsList; CometPositions[] cometPositions; MorphoPositions[] morphoPositions; MorphoVaultPositions[] morphoVaultPositions; } - // We map this to the Portfolio data structure that the client will already have. - // This includes fields that builder may not necessarily need, however it makes - // the client encoding that much simpler. - struct QuarkState { + struct QuarkSecret { address account; - uint96 quarkNextNonce; + bytes32 nonceSecret; } // Similarly, this is designed to intentionally reduce the encoding burden for the client @@ -182,16 +181,17 @@ library Accounts { return findAssetPositions(assetAddress, chainAccounts.assetPositionsList); } - function findQuarkState(address account, Accounts.QuarkState[] memory quarkStates) + function findQuarkSecret(address account, Accounts.QuarkSecret[] memory quarkSecrets) internal pure - returns (Accounts.QuarkState memory state) + returns (Accounts.QuarkSecret memory) { - for (uint256 i = 0; i < quarkStates.length; ++i) { - if (quarkStates[i].account == account) { - return state = quarkStates[i]; + for (uint256 i = 0; i < quarkSecrets.length; ++i) { + if (quarkSecrets[i].account == account) { + return quarkSecrets[i]; } } + revert QuarkSecretNotFound(account); } function findChainAccountsWithPaymentInfo( diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 2a736aa0..f2a24345 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {Accounts} from "./Accounts.sol"; import {BridgeRoutes, CCTP} from "./BridgeRoutes.sol"; @@ -61,6 +61,9 @@ library Actions { uint256 constant SWAP_EXPIRY_BUFFER = 3 days; uint256 constant TRANSFER_EXPIRY_BUFFER = 7 days; + /* total plays */ + uint256 constant RECURRING_SWAP_TOTAL_PLAYS = 500; + uint256 constant AVERAGE_BLOCK_TIME = 12 seconds; uint256 constant RECURRING_SWAP_MAX_SLIPPAGE = 1e17; // 1% @@ -244,6 +247,20 @@ library Actions { bool useQuotecall; } + // Note: To avoid stack too deep errors + struct RecurringSwapLocalVars { + Accounts.ChainAccounts accounts; + Accounts.AssetPositions sellTokenAssetPositions; + Accounts.AssetPositions buyTokenAssetPositions; + Accounts.QuarkSecret accountSecret; + } + + // Note: To avoid stack too deep errors + struct MorphoClaimLocalVars { + Accounts.ChainAccounts accounts; + Accounts.QuarkSecret accountSecret; + } + /* ===== Output Types ===== */ // With Action, we try to define fields that are as 1:1 as possible with @@ -260,6 +277,11 @@ library Actions { address paymentToken; string paymentTokenSymbol; uint256 paymentMaxCost; + // The secret used to generate the hash chain for a replayable operation. For non-replayable + // operations, the `nonce` will be the `nonceSecret` (the hash chain has a length of 1) + bytes32 nonceSecret; + // The number of times an operation can be played. For non-replayable operations, this will be 1 + uint256 totalPlays; } struct BorrowActionContext { @@ -609,14 +631,16 @@ library Actions { Accounts.AssetPositions memory srcUSDCPositions = Accounts.findAssetPositions("USDC", srcChainAccounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(bridge.sender, srcChainAccounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(bridge.sender, srcChainAccounts.quarkSecrets); bytes[] memory scriptSources = new bytes[](1); scriptSources[0] = CCTP.bridgeScriptSource(); // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(scriptSources[0]), scriptCalldata: CCTP.encodeBridgeUSDC( bridge.srcChainId, bridge.destinationChainId, bridge.amount, bridge.recipient, srcUSDCPositions.asset @@ -647,7 +671,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, bridge.srcChainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, bridge.srcChainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, bridge.srcChainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -664,7 +690,8 @@ library Actions { Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(borrowInput.chainId, borrowInput.chainAccountsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(borrowInput.borrower, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(borrowInput.borrower, accounts.quarkSecrets); Accounts.AssetPositions memory borrowAssetPositions = Accounts.findAssetPositions(borrowInput.assetSymbol, accounts.assetPositionsList); @@ -691,7 +718,8 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(CometSupplyMultipleAssetsAndBorrow).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -721,7 +749,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -738,7 +768,7 @@ library Actions { Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(repayInput.chainId, repayInput.chainAccountsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(repayInput.repayer, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(repayInput.repayer, accounts.quarkSecrets); Accounts.AssetPositions memory repayAssetPositions = Accounts.findAssetPositions(repayInput.assetSymbol, accounts.assetPositionsList); @@ -765,7 +795,8 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -795,7 +826,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -815,7 +848,7 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(cometSupply.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(cometSupply.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(cometSupply.sender, accounts.quarkSecrets); bytes memory scriptCalldata; if (Strings.stringEqIgnoreCase(cometSupply.assetSymbol, "ETH")) { @@ -827,7 +860,8 @@ library Actions { } // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(CometSupplyActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -853,7 +887,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, cometSupply.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometSupply.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometSupply.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -873,8 +909,8 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(cometWithdraw.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = - Accounts.findQuarkState(cometWithdraw.withdrawer, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(cometWithdraw.withdrawer, accounts.quarkSecrets); bytes memory scriptCalldata; if (Strings.stringEqIgnoreCase(cometWithdraw.assetSymbol, "ETH")) { @@ -886,7 +922,8 @@ library Actions { } // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(CometWithdrawActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -912,7 +949,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, cometWithdraw.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometWithdraw.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometWithdraw.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -932,7 +971,7 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(transfer.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(transfer.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(transfer.sender, accounts.quarkSecrets); bytes memory scriptCalldata; if (Strings.stringEqIgnoreCase(transfer.assetSymbol, "ETH")) { @@ -948,7 +987,8 @@ library Actions { } // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(TransferActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -975,7 +1015,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, transfer.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, transfer.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, transfer.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -992,7 +1034,8 @@ library Actions { Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(borrowInput.chainId, borrowInput.chainAccountsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(borrowInput.borrower, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(borrowInput.borrower, accounts.quarkSecrets); Accounts.AssetPositions memory borrowAssetPositions = Accounts.findAssetPositions(borrowInput.assetSymbol, accounts.assetPositionsList); @@ -1010,7 +1053,8 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -1042,7 +1086,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1059,7 +1105,7 @@ library Actions { Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(repayInput.chainId, repayInput.chainAccountsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(repayInput.repayer, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(repayInput.repayer, accounts.quarkSecrets); Accounts.AssetPositions memory repayAssetPositions = Accounts.findAssetPositions(repayInput.assetSymbol, accounts.assetPositionsList); @@ -1077,7 +1123,8 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -1110,7 +1157,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1130,7 +1179,7 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(vaultSupply.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(vaultSupply.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(vaultSupply.sender, accounts.quarkSecrets); bytes memory scriptCalldata = abi.encodeWithSelector( MorphoVaultActions.deposit.selector, @@ -1141,7 +1190,8 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoVaultActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -1167,7 +1217,9 @@ library Actions { // Null address for OFFCHAIN payment. paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, vaultSupply.chainId).token : address(0), paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, vaultSupply.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, vaultSupply.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1187,8 +1239,8 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(vaultWithdraw.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = - Accounts.findQuarkState(vaultWithdraw.withdrawer, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(vaultWithdraw.withdrawer, accounts.quarkSecrets); bytes memory scriptCalldata = abi.encodeWithSelector( MorphoVaultActions.withdraw.selector, @@ -1198,7 +1250,8 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoVaultActions).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -1226,7 +1279,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, vaultWithdraw.chainId).token : address(0), paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, vaultWithdraw.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, vaultWithdraw.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1237,49 +1292,70 @@ library Actions { pure returns (IQuarkWallet.QuarkOperation memory, Action memory) { - bytes[] memory scriptSources = new bytes[](1); - scriptSources[0] = type(MorphoRewardsActions).creationCode; - Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(claimRewards.chainId, claimRewards.chainAccountsList); - - Accounts.QuarkState memory accountState = Accounts.findQuarkState(claimRewards.claimer, accounts.quarkStates); - - List.DynamicArray memory rewardsPriceList = List.newList(); - List.DynamicArray memory rewardsSymbolList = List.newList(); - for (uint256 i = 0; i < claimRewards.rewards.length; i++) { + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(claimRewards.claimer, accounts.quarkSecrets); + // Alternative approach with local vars struct + // MorphoClaimLocalVars memory localVars; + // // Local scope to avoid stack too deep + // { + // Accounts.ChainAccounts memory accounts = + // Accounts.findChainAccounts(claimRewards.chainId, claimRewards.chainAccountsList); + // localVars = MorphoClaimLocalVars({ + // accounts: accounts, + // accountSecret: Accounts.findQuarkSecret(claimRewards.claimer, accounts.quarkSecrets) + // }); + // } + + // If you just don't initialize the following arrays, it compiles without stack too deep + // Uncomment lines 1313-1314. Comment out lines 1315-1323. + // string[] memory rewardsAssetSymbols; + // uint256[] memory rewardsPrices; + string[] memory rewardsAssetSymbols = new string[](claimRewards.rewards.length); + uint256[] memory rewardsPrices = new uint256[](claimRewards.rewards.length); + + for (uint256 i = 0; i < claimRewards.rewards.length; ++i) { Accounts.AssetPositions memory rewardsAssetPosition = Accounts.findAssetPositions(claimRewards.rewards[i], accounts.assetPositionsList); - List.addUint256(rewardsPriceList, rewardsAssetPosition.usdPrice); - List.addString(rewardsSymbolList, rewardsAssetPosition.symbol); + rewardsAssetSymbols[i] = rewardsAssetPosition.symbol; + rewardsPrices[i] = rewardsAssetPosition.usdPrice; } - bytes memory scriptCalldata = abi.encodeWithSelector( - MorphoRewardsActions.claimAll.selector, - claimRewards.distributors, - claimRewards.accounts, - claimRewards.rewards, - claimRewards.claimables, - claimRewards.proofs - ); - - // Construct QuarkOperation - IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, - scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoRewardsActions).creationCode), - scriptCalldata: scriptCalldata, - scriptSources: scriptSources, - expiry: claimRewards.blockTimestamp + STANDARD_EXPIRY_BUFFER - }); - MorphoClaimRewardsActionContext memory claimRewardsActionContext = MorphoClaimRewardsActionContext({ amounts: claimRewards.claimables, - assetSymbols: List.toStringArray(rewardsSymbolList), + assetSymbols: rewardsAssetSymbols, chainId: claimRewards.chainId, - prices: List.toUint256Array(rewardsPriceList), + prices: rewardsPrices, tokens: claimRewards.rewards }); + IQuarkWallet.QuarkOperation memory quarkOperation; + // Local scope to avoid stack too deep + { + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = type(MorphoRewardsActions).creationCode; + + bytes memory scriptCalldata = abi.encodeWithSelector( + MorphoRewardsActions.claimAll.selector, + claimRewards.distributors, + claimRewards.accounts, + claimRewards.rewards, + claimRewards.claimables, + claimRewards.proofs + ); + + // Construct QuarkOperation + quarkOperation = IQuarkWallet.QuarkOperation({ + nonce: accountSecret.nonceSecret, + isReplayable: false, + scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoRewardsActions).creationCode), + scriptCalldata: scriptCalldata, + scriptSources: scriptSources, + expiry: claimRewards.blockTimestamp + STANDARD_EXPIRY_BUFFER + }); + } + Action memory action = Actions.Action({ chainId: claimRewards.chainId, quarkAccount: claimRewards.claimer, @@ -1291,7 +1367,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, claimRewards.chainId).token : address(0), paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, claimRewards.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, claimRewards.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1311,10 +1389,11 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(wrapOrUnwrap.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(wrapOrUnwrap.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(wrapOrUnwrap.sender, accounts.quarkSecrets); // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode), scriptCalldata: TokenWrapper.encodeActionToWrapOrUnwrap( wrapOrUnwrap.chainId, wrapOrUnwrap.assetSymbol, wrapOrUnwrap.amount @@ -1344,7 +1423,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, wrapOrUnwrap.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, wrapOrUnwrap.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, wrapOrUnwrap.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1369,7 +1450,7 @@ library Actions { Accounts.AssetPositions memory feeTokenAssetPositions = Accounts.findAssetPositions(swap.feeAssetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(swap.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(swap.sender, accounts.quarkSecrets); // TODO: Handle wrapping ETH? Do we need to? bytes memory scriptCalldata = abi.encodeWithSelector( @@ -1384,7 +1465,8 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, + nonce: accountSecret.nonceSecret, + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(ApproveAndSwap).creationCode), scriptCalldata: scriptCalldata, scriptSources: scriptSources, @@ -1419,7 +1501,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, swap.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1430,55 +1514,68 @@ library Actions { pure returns (IQuarkWallet.QuarkOperation memory, Action memory) { - bytes[] memory scriptSources = new bytes[](1); - scriptSources[0] = type(RecurringSwap).creationCode; - - Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(swap.chainId, swap.chainAccountsList); - - Accounts.AssetPositions memory sellTokenAssetPositions = - Accounts.findAssetPositions(swap.sellAssetSymbol, accounts.assetPositionsList); - - Accounts.AssetPositions memory buyTokenAssetPositions = - Accounts.findAssetPositions(swap.buyAssetSymbol, accounts.assetPositionsList); - - Accounts.QuarkState memory accountState = Accounts.findQuarkState(swap.sender, accounts.quarkStates); + RecurringSwapLocalVars memory localVars; + // Local scope to avoid stack too deep + { + Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(swap.chainId, swap.chainAccountsList); + localVars = RecurringSwapLocalVars({ + accounts: accounts, + accountSecret: Accounts.findQuarkSecret(swap.sender, accounts.quarkSecrets), + sellTokenAssetPositions: Accounts.findAssetPositions(swap.sellAssetSymbol, accounts.assetPositionsList), + buyTokenAssetPositions: Accounts.findAssetPositions(swap.buyAssetSymbol, accounts.assetPositionsList) + }); + } - RecurringSwap.SwapParams memory swapParams = RecurringSwap.SwapParams({ - uniswapRouter: UniswapRouter.knownRouter(swap.chainId), - recipient: swap.sender, - tokenIn: swap.sellToken, - tokenOut: swap.buyToken, - amount: swap.isExactOut ? swap.buyAmount : swap.sellAmount, - isExactOut: swap.isExactOut, - path: swap.path - }); - (address[] memory priceFeeds, bool[] memory shouldInvert) = PriceFeeds.findPriceFeedPath({ - inputAssetSymbol: PriceFeeds.convertToPriceFeedSymbol(swap.sellAssetSymbol), - outputAssetSymbol: PriceFeeds.convertToPriceFeedSymbol(swap.buyAssetSymbol), - chainId: swap.chainId - }); - RecurringSwap.SlippageParams memory slippageParams = RecurringSwap.SlippageParams({ - maxSlippage: RECURRING_SWAP_MAX_SLIPPAGE, - priceFeeds: priceFeeds, - shouldInvert: shouldInvert - }); - RecurringSwap.SwapConfig memory swapConfig = RecurringSwap.SwapConfig({ - startTime: swap.blockTimestamp - AVERAGE_BLOCK_TIME, - interval: swap.interval, - swapParams: swapParams, - slippageParams: slippageParams - }); - // TODO: Handle wrapping ETH? Do we need to? - bytes memory scriptCalldata = abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig); + RecurringSwap.SwapConfig memory swapConfig; + // Local scope to avoid stack too deep + { + RecurringSwap.SwapParams memory swapParams = RecurringSwap.SwapParams({ + uniswapRouter: UniswapRouter.knownRouter(swap.chainId), + recipient: swap.sender, + tokenIn: swap.sellToken, + tokenOut: swap.buyToken, + amount: swap.isExactOut ? swap.buyAmount : swap.sellAmount, + isExactOut: swap.isExactOut, + path: swap.path + }); + (address[] memory priceFeeds, bool[] memory shouldInvert) = PriceFeeds.findPriceFeedPath({ + inputAssetSymbol: PriceFeeds.convertToPriceFeedSymbol(swap.sellAssetSymbol), + outputAssetSymbol: PriceFeeds.convertToPriceFeedSymbol(swap.buyAssetSymbol), + chainId: swap.chainId + }); + RecurringSwap.SlippageParams memory slippageParams = RecurringSwap.SlippageParams({ + maxSlippage: RECURRING_SWAP_MAX_SLIPPAGE, + priceFeeds: priceFeeds, + shouldInvert: shouldInvert + }); + swapConfig = RecurringSwap.SwapConfig({ + startTime: swap.blockTimestamp - AVERAGE_BLOCK_TIME, + interval: swap.interval, + swapParams: swapParams, + slippageParams: slippageParams + }); + } - // Construct QuarkOperation - IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: accountState.quarkNextNonce, - scriptAddress: CodeJarHelper.getCodeAddress(type(RecurringSwap).creationCode), - scriptCalldata: scriptCalldata, - scriptSources: scriptSources, - expiry: type(uint256).max - }); + IQuarkWallet.QuarkOperation memory quarkOperation; + // Local scope to avoid stack too deep + { + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = type(RecurringSwap).creationCode; + + // TODO: Handle wrapping ETH? Do we need to? + bytes memory scriptCalldata = abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig); + + bytes32 nonce = generateNonceFromSecret(localVars.accountSecret.nonceSecret, RECURRING_SWAP_TOTAL_PLAYS); + // Construct QuarkOperation + quarkOperation = IQuarkWallet.QuarkOperation({ + nonce: nonce, + isReplayable: true, + scriptAddress: CodeJarHelper.getCodeAddress(type(RecurringSwap).creationCode), + scriptCalldata: scriptCalldata, + scriptSources: scriptSources, + expiry: type(uint256).max + }); + } // Construct Action RecurringSwapActionContext memory recurringSwapActionContext = RecurringSwapActionContext({ @@ -1486,11 +1583,11 @@ library Actions { inputAmount: swap.sellAmount, inputAssetSymbol: swap.sellAssetSymbol, inputToken: swap.sellToken, - inputTokenPrice: sellTokenAssetPositions.usdPrice, + inputTokenPrice: localVars.sellTokenAssetPositions.usdPrice, outputAmount: swap.buyAmount, outputAssetSymbol: swap.buyAssetSymbol, outputToken: swap.buyToken, - outputTokenPrice: buyTokenAssetPositions.usdPrice, + outputTokenPrice: localVars.buyTokenAssetPositions.usdPrice, isExactOut: swap.isExactOut, interval: swap.interval }); @@ -1504,7 +1601,9 @@ library Actions { // Null address for OFFCHAIN payment. paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, swap.chainId).token : address(0), paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0, + nonceSecret: localVars.accountSecret.nonceSecret, + totalPlays: RECURRING_SWAP_TOTAL_PLAYS }); return (quarkOperation, action); @@ -1562,6 +1661,19 @@ library Actions { return result; } + function generateNonceFromSecret(bytes32 secret, uint256 totalPlays) internal pure returns (bytes32) { + uint256 replayCount = totalPlays - 1; + assembly ("memory-safe") { + let ptr := mload(0x40) // Get free memory pointer + mstore(ptr, secret) // Store initial secret at ptr + + for { let i := 0 } lt(i, replayCount) { i := add(i, 1) } { mstore(ptr, keccak256(ptr, 32)) } + + secret := mload(ptr) // Load final result + } + return secret; + } + // These structs are mostly used internally and returned in serialized format as bytes: actionContext // The caller can then decode them back into their struct form. // These empty husk functions exist so that the structs make it into the abi so the clients can know how to decode them. diff --git a/src/builder/BridgeRoutes.sol b/src/builder/BridgeRoutes.sol index 72936a2d..ca427da4 100644 --- a/src/builder/BridgeRoutes.sol +++ b/src/builder/BridgeRoutes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {QuarkBuilder} from "./QuarkBuilder.sol"; import {CCTPBridgeActions} from "../BridgeScripts.sol"; diff --git a/src/builder/CodeJarHelper.sol b/src/builder/CodeJarHelper.sol index 260c4af3..53c27690 100644 --- a/src/builder/CodeJarHelper.sol +++ b/src/builder/CodeJarHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; library CodeJarHelper { /* ===== Constants ===== */ diff --git a/src/builder/EIP712Helper.sol b/src/builder/EIP712Helper.sol index b865a3e2..53cb6426 100644 --- a/src/builder/EIP712Helper.sol +++ b/src/builder/EIP712Helper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; import {QuarkWalletMetadata} from "quark-core/src/QuarkWallet.sol"; @@ -90,6 +90,7 @@ library EIP712Helper { abi.encode( QUARK_OPERATION_TYPEHASH, op.nonce, + op.isReplayable, op.scriptAddress, keccak256(encodedScriptSources), keccak256(op.scriptCalldata), diff --git a/src/builder/Errors.sol b/src/builder/Errors.sol index ddad82ba..bcf88445 100644 --- a/src/builder/Errors.sol +++ b/src/builder/Errors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; /// Library of shared errors used across Quark Builder files library Errors { diff --git a/src/builder/HashMap.sol b/src/builder/HashMap.sol index 0b3038cd..a3c1723e 100644 --- a/src/builder/HashMap.sol +++ b/src/builder/HashMap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {List} from "./List.sol"; diff --git a/src/builder/List.sol b/src/builder/List.sol index 77f1217a..c42574a7 100644 --- a/src/builder/List.sol +++ b/src/builder/List.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; import {Actions} from "./Actions.sol"; diff --git a/src/builder/MorphoInfo.sol b/src/builder/MorphoInfo.sol index b0fe9b63..e3f69d2f 100644 --- a/src/builder/MorphoInfo.sol +++ b/src/builder/MorphoInfo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {MarketParams} from "src/interfaces/IMorpho.sol"; import {HashMap} from "./HashMap.sol"; diff --git a/src/builder/PaycallWrapper.sol b/src/builder/PaycallWrapper.sol index bd035f07..ff84c050 100644 --- a/src/builder/PaycallWrapper.sol +++ b/src/builder/PaycallWrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {CodeJarHelper} from "./CodeJarHelper.sol"; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; @@ -25,6 +25,7 @@ library PaycallWrapper { return IQuarkWallet.QuarkOperation({ nonce: operation.nonce, + isReplayable: operation.isReplayable, scriptAddress: CodeJarHelper.getCodeAddress(paycallSource), scriptCalldata: abi.encodeWithSelector( Paycall.run.selector, operation.scriptAddress, operation.scriptCalldata, maxPaymentCost diff --git a/src/builder/PaymentInfo.sol b/src/builder/PaymentInfo.sol index 93bf1f10..47225197 100644 --- a/src/builder/PaymentInfo.sol +++ b/src/builder/PaymentInfo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {Strings} from "./Strings.sol"; diff --git a/src/builder/PriceFeeds.sol b/src/builder/PriceFeeds.sol index 6efc8d89..54b8b142 100644 --- a/src/builder/PriceFeeds.sol +++ b/src/builder/PriceFeeds.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {Strings} from "./Strings.sol"; diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index fa8aaf8f..800024a9 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; @@ -20,7 +20,7 @@ import {List} from "./List.sol"; contract QuarkBuilder { /* ===== Constants ===== */ - string constant VERSION = "0.0.5"; + string constant VERSION = "0.1.1"; /* ===== Custom Errors ===== */ diff --git a/src/builder/QuarkBuilderHelper.sol b/src/builder/QuarkBuilderHelper.sol index 46e7a8da..0401e390 100644 --- a/src/builder/QuarkBuilderHelper.sol +++ b/src/builder/QuarkBuilderHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {BridgeRoutes} from "./BridgeRoutes.sol"; diff --git a/src/builder/QuarkOperationHelper.sol b/src/builder/QuarkOperationHelper.sol index a4c8d17b..6966f100 100644 --- a/src/builder/QuarkOperationHelper.sol +++ b/src/builder/QuarkOperationHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; @@ -101,6 +101,7 @@ library QuarkOperationHelper { IQuarkWallet.QuarkOperation memory lastQuarkOperation = quarkOperations[quarkOperations.length - 1]; IQuarkWallet.QuarkOperation memory mergedQuarkOperation = IQuarkWallet.QuarkOperation({ nonce: lastQuarkOperation.nonce, + isReplayable: lastQuarkOperation.isReplayable, scriptAddress: CodeJarHelper.getCodeAddress(type(Multicall).creationCode), scriptCalldata: multicallCalldata, scriptSources: scriptSources, diff --git a/src/builder/QuotecallWrapper.sol b/src/builder/QuotecallWrapper.sol index 90ff2fb0..7453e22b 100644 --- a/src/builder/QuotecallWrapper.sol +++ b/src/builder/QuotecallWrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import {CodeJarHelper} from "./CodeJarHelper.sol"; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; @@ -26,6 +26,7 @@ library QuotecallWrapper { return IQuarkWallet.QuarkOperation({ nonce: operation.nonce, + isReplayable: operation.isReplayable, scriptAddress: CodeJarHelper.getCodeAddress(quotecallSource), scriptCalldata: abi.encodeWithSelector( Quotecall.run.selector, operation.scriptAddress, operation.scriptCalldata, quotedAmount diff --git a/src/builder/Strings.sol b/src/builder/Strings.sol index 6d71f7b3..9b4c42f1 100644 --- a/src/builder/Strings.sol +++ b/src/builder/Strings.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; library Strings { function stringEq(string memory a, string memory b) internal pure returns (bool) { diff --git a/src/builder/TokenWrapper.sol b/src/builder/TokenWrapper.sol index 715166f5..31798b4a 100644 --- a/src/builder/TokenWrapper.sol +++ b/src/builder/TokenWrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "./Strings.sol"; import {WrapperActions} from "../WrapperScripts.sol"; diff --git a/src/builder/UniswapRouter.sol b/src/builder/UniswapRouter.sol index b444c116..82b20d76 100644 --- a/src/builder/UniswapRouter.sol +++ b/src/builder/UniswapRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; library UniswapRouter { error NoKnownRouter(string routerType, uint256 chainId); diff --git a/src/interfaces/IComet.sol b/src/interfaces/IComet.sol index 5f360c91..e203cd61 100644 --- a/src/interfaces/IComet.sol +++ b/src/interfaces/IComet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; interface IComet { function getAssetInfo(uint8 i) external view returns (AssetInfo memory); diff --git a/src/interfaces/ICometRewards.sol b/src/interfaces/ICometRewards.sol index 68d770fe..716fad9e 100644 --- a/src/interfaces/ICometRewards.sol +++ b/src/interfaces/ICometRewards.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; interface ICometRewards { function claim(address comet, address src, bool shouldAccrue) external; diff --git a/src/interfaces/IMetaMorpho.sol b/src/interfaces/IMetaMorpho.sol index 6ed71770..a2f97485 100644 --- a/src/interfaces/IMetaMorpho.sol +++ b/src/interfaces/IMetaMorpho.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IERC4626} from "openzeppelin/interfaces/IERC4626.sol"; diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 06b2a197..8837257d 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; /// @dev Interface for Morpho blue markets /// Reference: https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol diff --git a/src/interfaces/IMorphoUniversalRewardsDistributor.sol b/src/interfaces/IMorphoUniversalRewardsDistributor.sol index 8d9f8300..f6766d73 100644 --- a/src/interfaces/IMorphoUniversalRewardsDistributor.sol +++ b/src/interfaces/IMorphoUniversalRewardsDistributor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; /// @dev Interface for Morpho Universal Rewards Distributor /// Reference: https://github.com/morpho-org/universal-rewards-distributor/blob/main/src/UniversalRewardsDistributor.sol diff --git a/src/interfaces/ITokenMessenger.sol b/src/interfaces/ITokenMessenger.sol index 99c94ed1..40b6f503 100644 --- a/src/interfaces/ITokenMessenger.sol +++ b/src/interfaces/ITokenMessenger.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; interface ITokenMessenger { function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) diff --git a/src/interfaces/IWETH.sol b/src/interfaces/IWETH.sol index 53560bcd..53d3deb3 100644 --- a/src/interfaces/IWETH.sol +++ b/src/interfaces/IWETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; interface IWETH { function deposit() external payable; diff --git a/src/interfaces/IWstETH.sol b/src/interfaces/IWstETH.sol index 0be77e17..bc998672 100644 --- a/src/interfaces/IWstETH.sol +++ b/src/interfaces/IWstETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; interface IWstETH { function wrap(uint256 amount) external returns (uint256); diff --git a/src/lib/ConditionalChecker.sol b/src/lib/ConditionalChecker.sol index b8d92b36..1a0ceee0 100644 --- a/src/lib/ConditionalChecker.sol +++ b/src/lib/ConditionalChecker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; library ConditionalChecker { enum CheckType { diff --git a/src/lib/DeFiScriptErrors.sol b/src/lib/DeFiScriptErrors.sol index 660dbfd8..31cbaebb 100644 --- a/src/lib/DeFiScriptErrors.sol +++ b/src/lib/DeFiScriptErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; /** * @title Errors library for DeFi scripts diff --git a/src/lib/Math.sol b/src/lib/Math.sol index dcad7909..188947d1 100644 --- a/src/lib/Math.sol +++ b/src/lib/Math.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; library Math { function subtractFlooredAtZero(uint256 a, uint256 b) internal pure returns (uint256) { diff --git a/src/lib/UniswapFactoryAddress.sol b/src/lib/UniswapFactoryAddress.sol index 82d6b7b2..d154a4b7 100644 --- a/src/lib/UniswapFactoryAddress.sol +++ b/src/lib/UniswapFactoryAddress.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; library UniswapFactoryAddress { // Reference: https://docs.uniswap.org/contracts/v3/reference/deployments diff --git a/src/vendor/manifest.json b/src/vendor/manifest.json index 9140a980..8b190bf3 100644 --- a/src/vendor/manifest.json +++ b/src/vendor/manifest.json @@ -16,7 +16,7 @@ "newLines": 6, "lines": [ " // SPDX-License-Identifier: GPL-2.0-or-later", - "-pragma solidity 0.8.23;", + "-pragma solidity 0.8.27;", "+pragma solidity >=0.5.0;", " ", " /// @title Provides functions for deriving a pool address from the factory, tokens, and the fee", @@ -131,7 +131,7 @@ ] } ] - }, + }, "uniswap-swap-router-contracts/IApproveAndCall.sol": { "source": { "git": { @@ -205,9 +205,9 @@ "commit": "0448402af51b8293ed36653de43cbee8d4d2bfda", "path": "src/libraries/MathLib.sol" } - }, + }, "patches": [] - }, + }, "morpho_blue_periphery/SharesMathLib.sol":{ "source": { "git": { diff --git a/src/vendor/uniswap-v3-periphery/PoolAddress.sol b/src/vendor/uniswap-v3-periphery/PoolAddress.sol index 73eb80aa..1e3435af 100644 --- a/src/vendor/uniswap-v3-periphery/PoolAddress.sol +++ b/src/vendor/uniswap-v3-periphery/PoolAddress.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.23; +pragma solidity 0.8.27; /// @title Provides functions for deriving a pool address from the factory, tokens, and the fee library PoolAddress { diff --git a/test/ApproveAndSwap.t.sol b/test/ApproveAndSwap.t.sol index d56b1659..b1cac62b 100644 --- a/test/ApproveAndSwap.t.sol +++ b/test/ApproveAndSwap.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -8,7 +8,7 @@ import "src/DeFiScripts.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {YulHelper} from "./lib/YulHelper.sol"; import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; @@ -33,7 +33,7 @@ contract ApproveAndSwapTest is Test { // Warp to the block where the quote is valid 19121945 ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } // Tests a swap from USDC to WETH diff --git a/test/CCTPBridgeActions.t.sol b/test/CCTPBridgeActions.t.sol index 5cae3955..c6117473 100644 --- a/test/CCTPBridgeActions.t.sol +++ b/test/CCTPBridgeActions.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import "forge-std/StdMath.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -40,7 +40,7 @@ contract CCTPBridge is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testBridgeToBase() public { diff --git a/test/CometClaimRewards.t.sol b/test/CometClaimRewards.t.sol index e7010064..8bad92e0 100644 --- a/test/CometClaimRewards.t.sol +++ b/test/CometClaimRewards.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import "forge-std/StdMath.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -39,7 +39,7 @@ contract CometClaimRewardsTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testClaimComp() public { @@ -71,8 +71,10 @@ contract CometClaimRewardsTest is Test { ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); assertEq(IERC20(COMP).balanceOf(address(wallet)), 0e6); + vm.resumeGasMetering(); wallet.executeQuarkOperation(op, v, r, s); + assertGt(IERC20(COMP).balanceOf(address(wallet)), 0e6); } } diff --git a/test/CometRepayAndWithdrawMultipleAssets.t.sol b/test/CometRepayAndWithdrawMultipleAssets.t.sol index fea0d7c0..12c8c1c7 100644 --- a/test/CometRepayAndWithdrawMultipleAssets.t.sol +++ b/test/CometRepayAndWithdrawMultipleAssets.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import "forge-std/StdMath.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -40,7 +40,7 @@ contract CometRepayAndWithdrawMultipleAssetsTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testRepayAndWithdrawMultipleAssets() public { diff --git a/test/CometSupplyActions.t.sol b/test/CometSupplyActions.t.sol index f31dfd1d..fc9f6eb9 100644 --- a/test/CometSupplyActions.t.sol +++ b/test/CometSupplyActions.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import "forge-std/StdMath.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -42,7 +42,7 @@ contract SupplyActionsTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testSupply() public { diff --git a/test/CometSupplyMultipleAssetsAndBorrow.t.sol b/test/CometSupplyMultipleAssetsAndBorrow.t.sol index 45eabe2b..3b6f99d9 100644 --- a/test/CometSupplyMultipleAssetsAndBorrow.t.sol +++ b/test/CometSupplyMultipleAssetsAndBorrow.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import "forge-std/StdMath.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -40,7 +40,7 @@ contract CometSupplyMultipleAssetsAndBorrowTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testSupplyMultipleAssetsAndBorrow() public { diff --git a/test/CometWithdrawActions.t.sol b/test/CometWithdrawActions.t.sol index 624d8e7e..3d15e5f9 100644 --- a/test/CometWithdrawActions.t.sol +++ b/test/CometWithdrawActions.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import "forge-std/StdMath.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -41,7 +41,7 @@ contract WithdrawActionsTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testWithdraw() public { diff --git a/test/ConditionalMulticall.t.sol b/test/ConditionalMulticall.t.sol index 420e0379..8a24bb6e 100644 --- a/test/ConditionalMulticall.t.sol +++ b/test/ConditionalMulticall.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -10,7 +10,7 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -35,8 +35,7 @@ contract ConditionalMulticallTest is Test { address constant comet = 0xc3d688B66703497DAA19211EEdff47f25384cdc3; address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - // Uniswap router info on mainnet - address constant uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564; + bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); bytes conditionalMulticall = new YulHelper().getCode("ConditionalMulticall.sol/ConditionalMulticall.json"); address ethcallAddress; @@ -46,7 +45,7 @@ contract ConditionalMulticallTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); counter = new Counter(); counter.setNumber(0); ethcallAddress = QuarkWallet(payable(factory.walletImplementation())).codeJar().saveCode(ethcall); diff --git a/test/Ethcall.t.sol b/test/Ethcall.t.sol index feee4bc7..eef0a37f 100644 --- a/test/Ethcall.t.sol +++ b/test/Ethcall.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -11,7 +11,7 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -46,7 +46,7 @@ contract EthcallTest is Test { ); counter = new Counter(); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); counter.setNumber(0); QuarkWallet(payable(factory.walletImplementation())).codeJar().saveCode(ethcall); } diff --git a/test/GetDrip.t.sol b/test/GetDrip.t.sol index 92236b72..15290743 100644 --- a/test/GetDrip.t.sol +++ b/test/GetDrip.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {YulHelper} from "./lib/YulHelper.sol"; import {QuarkOperationHelper, ScriptType} from "./lib/QuarkOperationHelper.sol"; import {SignatureHelper} from "./lib/SignatureHelper.sol"; @@ -27,7 +27,7 @@ contract GetDripTest is Test { function setUp() public { // Fork setup vm.createSelectFork("https://sepolia.infura.io/v3/531e3eb124194de5a88caec726d10bea"); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } // Tests dripping some usdc diff --git a/test/MorphoActions.t.sol b/test/MorphoActions.t.sol index e73198ca..dfdc1055 100644 --- a/test/MorphoActions.t.sol +++ b/test/MorphoActions.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -11,7 +11,7 @@ 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 {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -44,7 +44,7 @@ contract MorphoActionsTest is Test { vm.envString("MAINNET_RPC_URL"), 20564787 // 2024-08-19 12:34:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testRepayAndWithdrawCollateral() public { diff --git a/test/MorphoRewardsActions.t.sol b/test/MorphoRewardsActions.t.sol index 69d00a2e..406944f0 100644 --- a/test/MorphoRewardsActions.t.sol +++ b/test/MorphoRewardsActions.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -11,7 +11,7 @@ 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 {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -83,7 +83,7 @@ contract MorphoRewardsActionsTest is Test { vm.envString("MAINNET_RPC_URL"), 20568177 // 2024-08-19 23:54:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testClaim() public { diff --git a/test/MorphoVaultActions.t.sol b/test/MorphoVaultActions.t.sol index d2093e06..239b6ae2 100644 --- a/test/MorphoVaultActions.t.sol +++ b/test/MorphoVaultActions.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ 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 {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -41,7 +41,7 @@ contract MorphoVaultActionsTest is Test { vm.envString("MAINNET_RPC_URL"), 20564787 // 2024-08-19 12:34:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testDeposit() public { diff --git a/test/Multicall.t.sol b/test/Multicall.t.sol index 692e4bf9..af62d873 100644 --- a/test/Multicall.t.sol +++ b/test/Multicall.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -8,7 +8,7 @@ import "forge-std/StdUtils.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -34,29 +34,29 @@ contract MulticallTest is Test { address constant cWETHv3 = 0xA17581A9E3356d9A858b789D68B4d866e593aE94; address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - // Uniswap SwapRouter02 info on mainnet + // Uniswap router info on mainnet address constant uniswapRouter = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); bytes multicall; - bytes legendCometSupplyScript = new YulHelper().getCode("DeFiScripts.sol/CometSupplyActions.json"); + bytes cometSupplyScript = new YulHelper().getCode("DeFiScripts.sol/CometSupplyActions.json"); - bytes legendCometWithdrawScript = new YulHelper().getCode("DeFiScripts.sol/CometWithdrawActions.json"); + bytes cometWithdrawScript = new YulHelper().getCode("DeFiScripts.sol/CometWithdrawActions.json"); - bytes legendUniswapSwapScript = new YulHelper().getCode("DeFiScripts.sol/UniswapSwapActions.json"); + bytes uniswapSwapScript = new YulHelper().getCode("DeFiScripts.sol/UniswapSwapActions.json"); address ethcallAddress; address multicallAddress; - address legendCometSupplyScriptAddress; - address legendCometWithdrawScriptAddress; - address legendUniswapSwapScriptAddress; + address cometSupplyScriptAddress; + address cometWithdrawScriptAddress; + address uniswapSwapScriptAddress; function setUp() public { vm.createSelectFork( vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); counter = new Counter(); counter.setNumber(0); @@ -64,9 +64,9 @@ contract MulticallTest is Test { ethcallAddress = codeJar.saveCode(ethcall); multicall = type(Multicall).creationCode; multicallAddress = codeJar.saveCode(multicall); - legendCometSupplyScriptAddress = codeJar.saveCode(legendCometSupplyScript); - legendCometWithdrawScriptAddress = codeJar.saveCode(legendCometWithdrawScript); - legendUniswapSwapScriptAddress = codeJar.saveCode(legendUniswapSwapScript); + cometSupplyScriptAddress = codeJar.saveCode(cometSupplyScript); + cometWithdrawScriptAddress = codeJar.saveCode(cometWithdrawScript); + uniswapSwapScriptAddress = codeJar.saveCode(uniswapSwapScript); } /* ===== call context-based tests ===== */ @@ -389,8 +389,10 @@ contract MulticallTest is Test { // 1. transfer 0.5 WETH from wallet A to wallet B wallets[0] = address(walletA); walletCalls[0] = abi.encodeWithSignature( - "executeScript(uint96,address,bytes,bytes[])", - QuarkWallet(payable(factory.walletImplementation())).stateManager().nextNonce(address(walletA)), + "executeScript(bytes32,address,bytes,bytes[])", + new QuarkOperationHelper().semiRandomNonce( + QuarkWallet(payable(factory.walletImplementation())).nonceManager(), walletA + ), ethcallAddress, abi.encodeWithSelector( Ethcall.run.selector, @@ -402,11 +404,12 @@ contract MulticallTest is Test { ); // 2. approve Comet cUSDCv3 to receive 0.5 WETH from wallet B - uint96 walletBNextNonce = - QuarkWallet(payable(factory.walletImplementation())).stateManager().nextNonce(address(walletB)); + bytes32 walletBNextNonce = new QuarkOperationHelper().semiRandomNonce( + QuarkWallet(payable(factory.walletImplementation())).nonceManager(), walletB + ); wallets[1] = address(walletB); walletCalls[1] = abi.encodeWithSignature( - "executeScript(uint96,address,bytes,bytes[])", + "executeScript(bytes32,address,bytes,bytes[])", walletBNextNonce, ethcallAddress, abi.encodeWithSelector( @@ -421,8 +424,8 @@ contract MulticallTest is Test { // 3. supply 0.5 WETH from wallet B to Comet cUSDCv3 wallets[2] = address(walletB); walletCalls[2] = abi.encodeWithSignature( - "executeScript(uint96,address,bytes,bytes[])", - walletBNextNonce + 1, + "executeScript(bytes32,address,bytes,bytes[])", + bytes32(uint256(walletBNextNonce) + 1), ethcallAddress, abi.encodeWithSelector( Ethcall.run.selector, @@ -491,15 +494,18 @@ contract MulticallTest is Test { deal(WETH, address(wallet), 100 ether); address subWallet1 = factory.walletAddressForSalt(alice, address(wallet), bytes32("1")); - uint96 nonce = QuarkWallet(payable(factory.walletImplementation())).stateManager().nextNonce(subWallet1); + bytes32 nonce = new QuarkOperationHelper().semiRandomNonce( + QuarkWallet(payable(factory.walletImplementation())).nonceManager(), QuarkWallet(payable(subWallet1)) + ); + // Steps: Wallet#1: Supply WETH to Comet -> Borrow USDC from Comet(USDC) to subwallet -> Create subwallet // -> Swap USDC to WETH on Uniswap -> Supply WETH to Comet(WETH) address[] memory callContracts = new address[](5); bytes[] memory callDatas = new bytes[](5); - callContracts[0] = legendCometSupplyScriptAddress; + callContracts[0] = cometSupplyScriptAddress; callDatas[0] = abi.encodeCall(CometSupplyActions.supply, (cUSDCv3, WETH, 100 ether)); - callContracts[1] = legendCometWithdrawScriptAddress; + callContracts[1] = cometWithdrawScriptAddress; callDatas[1] = abi.encodeCall(CometWithdrawActions.withdrawTo, (cUSDCv3, subWallet1, USDC, 10_000e6)); callContracts[2] = ethcallAddress; @@ -518,7 +524,7 @@ contract MulticallTest is Test { QuarkWallet.executeScript, ( nonce, - legendUniswapSwapScriptAddress, + uniswapSwapScriptAddress, abi.encodeCall( UniswapSwapActions.swapAssetExactIn, ( @@ -545,8 +551,8 @@ contract MulticallTest is Test { abi.encodeCall( QuarkWallet.executeScript, ( - nonce + 1, - legendCometSupplyScriptAddress, + new QuarkOperationHelper().incrementNonce(nonce), + cometSupplyScriptAddress, abi.encodeCall(CometSupplyActions.supply, (cWETHv3, WETH, 2 ether)), new bytes[](0) ) diff --git a/test/Paycall.t.sol b/test/Paycall.t.sol index cbdc8042..f1e81a07 100644 --- a/test/Paycall.t.sol +++ b/test/Paycall.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -8,7 +8,7 @@ import "forge-std/StdUtils.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -48,8 +48,6 @@ contract PaycallTest is Test { address constant ETH_USD_PRICE_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; address constant ETH_BTC_PRICE_FEED = 0xAc559F25B1619171CbC396a50854A3240b6A4e99; - // Uniswap router info on mainnet - address constant uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564; bytes multicall = new YulHelper().getCode("Multicall.sol/Multicall.json"); bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); bytes reverts = new YulHelper().getCode("Reverts.sol/Reverts.json"); @@ -59,11 +57,11 @@ contract PaycallTest is Test { bytes paycallUSDT; bytes paycallWBTC; - bytes legendCometSupplyScript = new YulHelper().getCode("DeFiScripts.sol/CometSupplyActions.json"); + bytes cometSupplyScript = new YulHelper().getCode("DeFiScripts.sol/CometSupplyActions.json"); - bytes legendCometWithdrawScript = new YulHelper().getCode("DeFiScripts.sol/CometWithdrawActions.json"); + bytes cometWithdrawScript = new YulHelper().getCode("DeFiScripts.sol/CometWithdrawActions.json"); - bytes legendUniswapSwapScript = new YulHelper().getCode("DeFiScripts.sol/UniswapSwapActions.json"); + bytes uniswapSwapScript = new YulHelper().getCode("DeFiScripts.sol/UniswapSwapActions.json"); address ethcallAddress; address multicallAddress; @@ -71,16 +69,16 @@ contract PaycallTest is Test { address paycallAddress; address paycallUSDTAddress; address paycallWBTCAddress; - address legendCometSupplyScriptAddress; - address legendCometWithdrawScriptAddress; - address legendUniswapSwapScriptAddress; + address cometSupplyScriptAddress; + address cometWithdrawScriptAddress; + address uniswapSwapScriptAddress; function setUp() public { vm.createSelectFork( vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); counter = new Counter(); counter.setNumber(0); @@ -96,9 +94,9 @@ contract PaycallTest is Test { paycallUSDTAddress = codeJar.saveCode(paycallUSDT); paycallWBTCAddress = codeJar.saveCode(paycallWBTC); - legendCometSupplyScriptAddress = codeJar.saveCode(legendCometSupplyScript); - legendCometWithdrawScriptAddress = codeJar.saveCode(legendCometWithdrawScript); - legendUniswapSwapScriptAddress = codeJar.saveCode(legendUniswapSwapScript); + cometSupplyScriptAddress = codeJar.saveCode(cometSupplyScript); + cometWithdrawScriptAddress = codeJar.saveCode(cometWithdrawScript); + uniswapSwapScriptAddress = codeJar.saveCode(uniswapSwapScript); } /* ===== call context-based tests ===== */ @@ -418,7 +416,7 @@ contract PaycallTest is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); vm.resumeGasMetering(); - vm.expectRevert(abi.encodeWithSelector(Paycall.TransactionTooExpensive.selector, 5000000, 10485677)); + vm.expectRevert(abi.encodeWithSelector(Paycall.TransactionTooExpensive.selector, 5_000_000, 10_478_744)); wallet.executeQuarkOperation(op, v, r, s); assertEq(IERC20(USDC).balanceOf(address(wallet)), 1000e6); @@ -459,6 +457,6 @@ contract PaycallTest is Test { ); CodeJar mainnetCodejar = CodeJar(CodeJarHelper.CODE_JAR_ADDRESS); - assertEq(mainnetCodejar.getCodeAddress(paycall), 0x1aF2e314e3291c6a4cD52A210a1AF027c7799cce); + assertEq(mainnetCodejar.getCodeAddress(paycall), 0x77641fB52eadE523c4d548aB931514e0Ad559Bf0); } } diff --git a/test/PaycallWrapper.t.sol b/test/PaycallWrapper.t.sol index 3eee18b5..b1df8099 100644 --- a/test/PaycallWrapper.t.sol +++ b/test/PaycallWrapper.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -7,7 +7,7 @@ import "forge-std/StdUtils.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; @@ -15,6 +15,8 @@ import {Paycall} from "src/Paycall.sol"; import {PaycallWrapper} from "src/builder/PaycallWrapper.sol"; import {TransferActions} from "src/DeFiScripts.sol"; +import {QuarkOperationHelper} from "test/lib/QuarkOperationHelper.sol"; + contract PaycallWrapperTest is Test { QuarkWalletProxyFactory public factory; uint256 alicePrivateKey = 0xa11ce; @@ -25,7 +27,7 @@ contract PaycallWrapperTest is Test { address constant ETH_USD_PRICE_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; function setUp() public { - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); codeJar = QuarkWallet(payable(factory.walletImplementation())).codeJar(); } @@ -36,7 +38,8 @@ contract PaycallWrapperTest is Test { bytes[] memory scriptSources = new bytes[](1); scriptSources[0] = type(TransferActions).creationCode; IQuarkWallet.QuarkOperation memory op = IQuarkWallet.QuarkOperation({ - nonce: wallet.stateManager().nextNonce(address(wallet)), + nonce: new QuarkOperationHelper().semiRandomNonce(wallet), + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(TransferActions).creationCode), scriptCalldata: abi.encodeWithSelector(TransferActions.transferERC20Token.selector, USDC, address(this), 10e6), scriptSources: scriptSources, diff --git a/test/Quotecall.t.sol b/test/Quotecall.t.sol index 4c080f02..35464c79 100644 --- a/test/Quotecall.t.sol +++ b/test/Quotecall.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -8,7 +8,7 @@ import "forge-std/StdUtils.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -47,8 +47,6 @@ contract QuotecallTest is Test { address constant ETH_USD_PRICE_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; address constant ETH_BTC_PRICE_FEED = 0xAc559F25B1619171CbC396a50854A3240b6A4e99; - // Uniswap router info on mainnet - address constant uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564; bytes multicall = new YulHelper().getCode("Multicall.sol/Multicall.json"); bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); bytes reverts = new YulHelper().getCode("Reverts.sol/Reverts.json"); @@ -59,11 +57,11 @@ contract QuotecallTest is Test { bytes quotecallUSDT; bytes quotecallWBTC; - bytes legendCometSupplyScript = new YulHelper().getCode("DeFiScripts.sol/CometSupplyActions.json"); + bytes cometSupplyScript = new YulHelper().getCode("DeFiScripts.sol/CometSupplyActions.json"); - bytes legendCometWithdrawScript = new YulHelper().getCode("DeFiScripts.sol/CometWithdrawActions.json"); + bytes cometWithdrawScript = new YulHelper().getCode("DeFiScripts.sol/CometWithdrawActions.json"); - bytes legendUniswapSwapScript = new YulHelper().getCode("DeFiScripts.sol/UniswapSwapActions.json"); + bytes uniswapSwapScript = new YulHelper().getCode("DeFiScripts.sol/UniswapSwapActions.json"); address ethcallAddress; address multicallAddress; @@ -71,16 +69,16 @@ contract QuotecallTest is Test { address quotecallAddress; address quotecallUSDTAddress; address quotecallWBTCAddress; - address legendCometSupplyScriptAddress; - address legendCometWithdrawScriptAddress; - address legendUniswapSwapScriptAddress; + address cometSupplyScriptAddress; + address cometWithdrawScriptAddress; + address uniswapSwapScriptAddress; function setUp() public { vm.createSelectFork( vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); counter = new Counter(); counter.setNumber(0); @@ -100,9 +98,9 @@ contract QuotecallTest is Test { quotecallUSDTAddress = codeJar.saveCode(quotecallUSDT); quotecallWBTCAddress = codeJar.saveCode(quotecallWBTC); - legendCometSupplyScriptAddress = codeJar.saveCode(legendCometSupplyScript); - legendCometWithdrawScriptAddress = codeJar.saveCode(legendCometWithdrawScript); - legendUniswapSwapScriptAddress = codeJar.saveCode(legendUniswapSwapScript); + cometSupplyScriptAddress = codeJar.saveCode(cometSupplyScript); + cometWithdrawScriptAddress = codeJar.saveCode(cometWithdrawScript); + uniswapSwapScriptAddress = codeJar.saveCode(uniswapSwapScript); } /* ===== call context-based tests ===== */ diff --git a/test/QuotecallWrapper.t.sol b/test/QuotecallWrapper.t.sol index 311407cb..f49ccdae 100644 --- a/test/QuotecallWrapper.t.sol +++ b/test/QuotecallWrapper.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -7,7 +7,7 @@ import "forge-std/StdUtils.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol"; import {CodeJarHelper} from "src/builder/CodeJarHelper.sol"; @@ -15,6 +15,8 @@ import {Quotecall} from "src/Quotecall.sol"; import {QuotecallWrapper} from "src/builder/QuotecallWrapper.sol"; import {TransferActions} from "src/DeFiScripts.sol"; +import {QuarkOperationHelper} from "test/lib/QuarkOperationHelper.sol"; + contract QuotecallWrapperTest is Test { QuarkWalletProxyFactory public factory; uint256 alicePrivateKey = 0xa11ce; @@ -25,7 +27,7 @@ contract QuotecallWrapperTest is Test { address constant ETH_USD_PRICE_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; function setUp() public { - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); codeJar = QuarkWallet(payable(factory.walletImplementation())).codeJar(); } @@ -36,7 +38,8 @@ contract QuotecallWrapperTest is Test { bytes[] memory scriptSources = new bytes[](1); scriptSources[0] = type(TransferActions).creationCode; IQuarkWallet.QuarkOperation memory op = IQuarkWallet.QuarkOperation({ - nonce: wallet.stateManager().nextNonce(address(wallet)), + nonce: new QuarkOperationHelper().semiRandomNonce(wallet), + isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(TransferActions).creationCode), scriptCalldata: abi.encodeWithSelector(TransferActions.transferERC20Token.selector, USDC, address(this), 10e6), scriptSources: scriptSources, diff --git a/test/RecurringSwap.t.sol b/test/RecurringSwap.t.sol index 418ebf39..d205886a 100644 --- a/test/RecurringSwap.t.sol +++ b/test/RecurringSwap.t.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; import {QuarkMinimalProxy} from "quark-proxy/src/QuarkMinimalProxy.sol"; +import {Cancel} from "src/Cancel.sol"; import {RecurringSwap} from "src/RecurringSwap.sol"; import {YulHelper} from "./lib/YulHelper.sol"; @@ -31,7 +32,7 @@ contract RecurringSwapTest is Test { ); CodeJar public codeJar; - QuarkStateManager public stateManager; + QuarkNonceManager public nonceManager; QuarkWallet public walletImplementation; uint256 alicePrivateKey = 0x8675309; @@ -60,10 +61,10 @@ contract RecurringSwapTest is Test { codeJar = new CodeJar(); console.log("CodeJar deployed to: %s", address(codeJar)); - stateManager = new QuarkStateManager(); - console.log("QuarkStateManager deployed to: %s", address(stateManager)); + nonceManager = new QuarkNonceManager(); + console.log("QuarkNonceManager deployed to: %s", address(nonceManager)); - walletImplementation = new QuarkWallet(codeJar, stateManager); + walletImplementation = new QuarkWallet(codeJar, nonceManager); console.log("QuarkWallet implementation: %s", address(walletImplementation)); aliceWallet = @@ -303,11 +304,13 @@ contract RecurringSwapTest is Test { amount: amountToSwap, isExactOut: true }); - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + (QuarkWallet.QuarkOperation memory op, bytes32[] memory submissionTokens) = new QuarkOperationHelper() + .newReplayableOpWithCalldata( aliceWallet, recurringSwap, abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig), - ScriptType.ScriptAddress + ScriptType.ScriptAddress, + 1 ); op.expiry = type(uint256).max; (uint8 v1, bytes32 r1, bytes32 s1) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); @@ -327,11 +330,11 @@ contract RecurringSwapTest is Test { RecurringSwap.SwapWindowNotOpen.selector, block.timestamp + swapInterval, block.timestamp ) ); - aliceWallet.executeQuarkOperation(op, v1, r1, s1); + aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[1], v1, r1, s1); // 2b. Execute recurring swap a second time after warping 1 day vm.warp(block.timestamp + swapInterval); - aliceWallet.executeQuarkOperation(op, v1, r1, s1); + aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[1], v1, r1, s1); assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 2 * amountToSwap); } @@ -349,18 +352,19 @@ contract RecurringSwapTest is Test { amount: amountToSwap, isExactOut: true }); - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + (QuarkWallet.QuarkOperation memory op, bytes32[] memory submissionTokens) = new QuarkOperationHelper() + .newReplayableOpWithCalldata( aliceWallet, recurringSwap, abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig), - ScriptType.ScriptAddress + ScriptType.ScriptAddress, + 2 ); op.expiry = type(uint256).max; (uint8 v1, bytes32 r1, bytes32 s1) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); - QuarkWallet.QuarkOperation memory cancelOp = new QuarkOperationHelper().newBasicOpWithCalldata( - aliceWallet, recurringSwap, abi.encodeWithSelector(RecurringSwap.cancel.selector), ScriptType.ScriptAddress - ); + QuarkWallet.QuarkOperation memory cancelOp = new QuarkOperationHelper().cancelReplayableByNop(aliceWallet, op); + cancelOp.nonce = op.nonce; (uint8 v2, bytes32 r2, bytes32 s2) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, cancelOp); assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 0 ether); @@ -373,12 +377,23 @@ contract RecurringSwapTest is Test { assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), amountToSwap); // 2. Cancel replayable transaction - aliceWallet.executeQuarkOperation(cancelOp, v2, r2, s2); + aliceWallet.executeQuarkOperationWithSubmissionToken(cancelOp, submissionTokens[1], v2, r2, s2); // 3. Replayable transaction can no longer be executed vm.warp(block.timestamp + swapInterval); - vm.expectRevert(QuarkStateManager.NonceAlreadySet.selector); - aliceWallet.executeQuarkOperation(op, v1, r1, s1); + vm.expectRevert( + abi.encodeWithSelector( + QuarkNonceManager.NonReplayableNonce.selector, address(aliceWallet), op.nonce, submissionTokens[1] + ) + ); + aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[1], v1, r1, s1); + + vm.expectRevert( + abi.encodeWithSelector( + QuarkNonceManager.NonReplayableNonce.selector, address(aliceWallet), op.nonce, submissionTokens[2] + ) + ); + aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[2], v1, r1, s1); assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), amountToSwap); } @@ -433,6 +448,7 @@ contract RecurringSwapTest is Test { uint40 swapInterval = 86_400; // 1 day interval uint256 amountToSwap1 = 10 ether; uint256 amountToSwap2 = 5 ether; + bytes32[] memory submissionTokens; QuarkWallet.QuarkOperation memory op1; QuarkWallet.QuarkOperation memory op2; QuarkWallet.QuarkOperation memory cancelOp; @@ -445,11 +461,12 @@ contract RecurringSwapTest is Test { amount: amountToSwap1, isExactOut: true }); - op1 = new QuarkOperationHelper().newBasicOpWithCalldata( + (op1, submissionTokens) = new QuarkOperationHelper().newReplayableOpWithCalldata( aliceWallet, recurringSwap, abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig1), - ScriptType.ScriptAddress + ScriptType.ScriptAddress, + 5 ); op1.expiry = type(uint256).max; RecurringSwap.SwapConfig memory swapConfig2 = _createSwapConfig({ @@ -465,13 +482,11 @@ contract RecurringSwapTest is Test { ScriptType.ScriptAddress ); op2.expiry = type(uint256).max; - cancelOp = new QuarkOperationHelper().newBasicOpWithCalldata( - aliceWallet, - recurringSwap, - abi.encodeWithSelector(RecurringSwap.cancel.selector), - ScriptType.ScriptAddress - ); + op2.nonce = op1.nonce; + op2.isReplayable = true; + cancelOp = new QuarkOperationHelper().cancelReplayableByNop(aliceWallet, op1); cancelOp.expiry = type(uint256).max; + cancelOp.nonce = op1.nonce; } (uint8 v1, bytes32 r1, bytes32 s1) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op1); (uint8 v2, bytes32 r2, bytes32 s2) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op2); @@ -487,7 +502,7 @@ contract RecurringSwapTest is Test { assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), amountToSwap1); // 1b. Execute recurring swap order #2 - aliceWallet.executeQuarkOperation(op2, v2, r2, s2); + aliceWallet.executeQuarkOperationWithSubmissionToken(op2, submissionTokens[1], v2, r2, s2); assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), amountToSwap1 + amountToSwap2); @@ -495,26 +510,34 @@ contract RecurringSwapTest is Test { vm.warp(block.timestamp + swapInterval); // 3a. Execute recurring swap order #1 - aliceWallet.executeQuarkOperation(op1, v1, r1, s1); + aliceWallet.executeQuarkOperationWithSubmissionToken(op1, submissionTokens[2], v1, r1, s1); assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 2 * amountToSwap1 + amountToSwap2); // 3b. Execute recurring swap order #2 - aliceWallet.executeQuarkOperation(op2, v2, r2, s2); + aliceWallet.executeQuarkOperationWithSubmissionToken(op2, submissionTokens[3], v2, r2, s2); assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 2 * amountToSwap1 + 2 * amountToSwap2); // 4. Cancel replayable transaction - aliceWallet.executeQuarkOperation(cancelOp, v3, r3, s3); + aliceWallet.executeQuarkOperationWithSubmissionToken(cancelOp, submissionTokens[4], v3, r3, s3); // 5. Warp until next swap period vm.warp(block.timestamp + swapInterval); // 6. Both recurring swap orders can no longer be executed - vm.expectRevert(QuarkStateManager.NonceAlreadySet.selector); - aliceWallet.executeQuarkOperation(op1, v1, r1, s1); - vm.expectRevert(QuarkStateManager.NonceAlreadySet.selector); - aliceWallet.executeQuarkOperation(op2, v2, r2, s2); + vm.expectRevert( + abi.encodeWithSelector( + QuarkNonceManager.NonReplayableNonce.selector, address(aliceWallet), op1.nonce, submissionTokens[4] + ) + ); + aliceWallet.executeQuarkOperationWithSubmissionToken(op1, submissionTokens[4], v1, r1, s1); + vm.expectRevert( + abi.encodeWithSelector( + QuarkNonceManager.NonReplayableNonce.selector, address(aliceWallet), op2.nonce, submissionTokens[5] + ) + ); + aliceWallet.executeQuarkOperationWithSubmissionToken(op2, submissionTokens[5], v2, r2, s2); assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), 2 * amountToSwap1 + 2 * amountToSwap2); } @@ -586,11 +609,13 @@ contract RecurringSwapTest is Test { amount: amountToSwap, isExactOut: true }); - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( + (QuarkWallet.QuarkOperation memory op, bytes32[] memory submissionTokens) = new QuarkOperationHelper() + .newReplayableOpWithCalldata( aliceWallet, recurringSwap, abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig), - ScriptType.ScriptAddress + ScriptType.ScriptAddress, + 1 ); (uint8 v1, bytes32 r1, bytes32 s1) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); @@ -609,7 +634,7 @@ contract RecurringSwapTest is Test { RecurringSwap.SwapWindowNotOpen.selector, block.timestamp + swapInterval, block.timestamp ) ); - aliceWallet.executeQuarkOperation(op, v1, r1, s1); + aliceWallet.executeQuarkOperationWithSubmissionToken(op, submissionTokens[1], v1, r1, s1); assertEq(IERC20(WETH).balanceOf(address(aliceWallet)), amountToSwap); } diff --git a/test/TransferActions.t.sol b/test/TransferActions.t.sol index 014f867c..40aac16b 100644 --- a/test/TransferActions.t.sol +++ b/test/TransferActions.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -10,7 +10,7 @@ import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkScript} from "quark-core/src/QuarkScript.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -53,7 +53,7 @@ contract TransferActionsTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); codeJar = QuarkWallet(payable(factory.walletImplementation())).codeJar(); } @@ -442,7 +442,9 @@ contract TransferActionsTest is Test { vm.expectRevert( abi.encodeWithSelector( DeFiScriptErrors.TransferFailed.selector, - abi.encodeWithSelector(QuarkStateManager.NonceAlreadySet.selector) + abi.encodeWithSelector( + QuarkNonceManager.NonReplayableNonce.selector, address(wallet), op.nonce, op.nonce + ) ) ); wallet.executeQuarkOperation(op, v, r, s); diff --git a/test/UniswapFlashLoan.t.sol b/test/UniswapFlashLoan.t.sol index 8745b49d..b894cb17 100644 --- a/test/UniswapFlashLoan.t.sol +++ b/test/UniswapFlashLoan.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -10,7 +10,7 @@ import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -54,7 +54,7 @@ contract UniswapFlashLoanTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); CodeJar codeJar = QuarkWallet(payable(factory.walletImplementation())).codeJar(); ethcallAddress = codeJar.saveCode(ethcall); multicallAddress = codeJar.saveCode(multicall); @@ -309,24 +309,4 @@ contract UniswapFlashLoanTest is Test { // Lose 1 USDC to flash loan fee assertEq(IERC20(USDC).balanceOf(address(wallet)), 9998e6); } - - function testRevertsIfCalledDirectly() public { - // gas: do not meter set-up - vm.pauseGasMetering(); - UniswapFlashLoan.UniswapFlashLoanPayload memory payload = UniswapFlashLoan.UniswapFlashLoanPayload({ - token0: USDC, - token1: DAI, - fee: 100, - amount0: 0, - amount1: 0, - callContract: address(0), - callData: bytes("") - }); - - // gas: meter execute - vm.resumeGasMetering(); - // Reverts when calling `allowCallback()`, which tries to get the `stateManager` from self - vm.expectRevert(); - UniswapFlashLoan(uniswapFlashLoanAddress).run(payload); - } } diff --git a/test/UniswapFlashSwapExactOut.t.sol b/test/UniswapFlashSwapExactOut.t.sol index 3639c1f7..cca5e466 100644 --- a/test/UniswapFlashSwapExactOut.t.sol +++ b/test/UniswapFlashSwapExactOut.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -10,7 +10,7 @@ import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -50,7 +50,7 @@ contract UniswapFlashSwapExactOutTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); ethcallAddress = QuarkWallet(payable(factory.walletImplementation())).codeJar().saveCode(ethcall); multicallAddress = QuarkWallet(payable(factory.walletImplementation())).codeJar().saveCode(multicall); uniswapFlashSwapExactOutAddress = @@ -209,7 +209,7 @@ contract UniswapFlashSwapExactOutTest is Test { // gas: meter execute vm.resumeGasMetering(); - // Reverts when calling `allowCallback()`, which tries to get the `stateManager` from self + // Reverts when calling `allowCallback()`, which tries to get the `nonceManager` from self vm.expectRevert(); UniswapFlashSwapExactOut(uniswapFlashSwapExactOutAddress).run(payload); } diff --git a/test/UniswapSwapActions.t.sol b/test/UniswapSwapActions.t.sol index bd70b2ac..b7e61025 100644 --- a/test/UniswapSwapActions.t.sol +++ b/test/UniswapSwapActions.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import "forge-std/StdMath.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -45,7 +45,7 @@ contract UniswapSwapActionsTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } // Usually one stop is sufficient for pairs with high liquidity diff --git a/test/WrapperScripts.t.sol b/test/WrapperScripts.t.sol index 947117a4..4adc5745 100644 --- a/test/WrapperScripts.t.sol +++ b/test/WrapperScripts.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -9,7 +9,7 @@ import "forge-std/StdMath.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; import {QuarkWallet} from "quark-core/src/QuarkWallet.sol"; -import {QuarkStateManager} from "quark-core/src/QuarkStateManager.sol"; +import {QuarkNonceManager} from "quark-core/src/QuarkNonceManager.sol"; import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.sol"; @@ -40,7 +40,7 @@ contract WrapperScriptsTest is Test { vm.envString("MAINNET_RPC_URL"), 18429607 // 2023-10-25 13:24:00 PST ); - factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkStateManager()))); + factory = new QuarkWalletProxyFactory(address(new QuarkWallet(new CodeJar(), new QuarkNonceManager()))); } function testWrapETH() public { diff --git a/test/builder/QuarkBuilderCometBorrow.t.sol b/test/builder/QuarkBuilderCometBorrow.t.sol index 5eb529dc..f6d36eda 100644 --- a/test/builder/QuarkBuilderCometBorrow.t.sol +++ b/test/builder/QuarkBuilderCometBorrow.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -84,7 +84,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK cometPortfolios: emptyCometPortfolios_(), @@ -94,7 +94,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -154,6 +154,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -163,6 +165,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -209,7 +213,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 10e18, 0, 0), // user has 10 ETH cometPortfolios: emptyCometPortfolios_(), @@ -219,7 +223,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -275,6 +279,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -284,6 +290,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -320,7 +328,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 10e18, 0), // user has 1 USDC, 10 LINK cometPortfolios: emptyCometPortfolios_(), @@ -330,7 +338,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -397,6 +405,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -406,6 +416,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -450,7 +462,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK and 0 USDC cometPortfolios: emptyCometPortfolios_(), @@ -460,7 +472,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -522,6 +534,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -531,6 +545,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -577,7 +593,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(3e6, 0, 0, 0), // 3 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -586,8 +602,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, - account: address(0xb0b), - nextNonce: 2, + account: address(0xa11ce), + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 5e18, 0), cometPortfolios: emptyCometPortfolios_(), @@ -649,6 +665,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -686,6 +704,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, chainPortfolios[1].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -696,6 +716,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -719,6 +741,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[1].nonceSecret, chainPortfolios[1].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -765,7 +789,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -774,8 +798,9 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, - account: address(0xb0b), - nextNonce: 2, + // TODO: if want to test different accounts, can set bridge as b0b and send as allice + account: address(0xa11ce), + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), @@ -837,6 +862,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -874,6 +901,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, chainPortfolios[1].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -884,6 +913,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -907,6 +938,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[1].nonceSecret, chainPortfolios[1].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = USDC_PRICE; diff --git a/test/builder/QuarkBuilderCometRepay.t.sol b/test/builder/QuarkBuilderCometRepay.t.sol index a153b320..111f25aa 100644 --- a/test/builder/QuarkBuilderCometRepay.t.sol +++ b/test/builder/QuarkBuilderCometRepay.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -83,7 +83,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 0, 1e18), // user does not have enough USDC cometPortfolios: emptyCometPortfolios_(), @@ -111,7 +111,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC cometPortfolios: emptyCometPortfolios_(), @@ -121,7 +121,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -182,6 +182,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -191,6 +193,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -237,7 +241,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 1e18, 0, 0), // has 1 ETH cometPortfolios: emptyCometPortfolios_(), @@ -247,7 +251,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -305,6 +309,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -314,6 +320,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -350,7 +358,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(2e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -360,7 +368,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -428,6 +436,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -437,6 +447,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -472,17 +484,11 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 1e18), cometPortfolios: emptyCometPortfolios_(), @@ -492,7 +498,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -500,15 +506,26 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { morphoVaultPortfolios: emptyMorphoVaultPortfolios_() }); - QuarkBuilder.BuilderResult memory result = builder.cometRepay( - repayIntent_( + QuarkBuilder.CometRepayIntent memory repayIntent; + // Local scope to avoid stack too deep + { + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 1e6; + + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "USDC"; + + repayIntent = repayIntent_( 1, cometWeth_(1), "WETH", 1e18, // repaying 1 WETH collateralAssetSymbols, collateralAmounts // and withdrawing 1 USDC - ), + ); + } + QuarkBuilder.BuilderResult memory result = builder.cometRepay( + repayIntent, chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) // user is paying with USDC that is currently supplied as collateral ); @@ -538,7 +555,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { CometRepayAndWithdrawMultipleAssets.run.selector, cometWeth_(1), collateralTokens, - collateralAmounts, + repayIntent.collateralAmounts, weth_(1), 1e18 ), @@ -556,6 +573,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -565,6 +584,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = USDC_PRICE; @@ -576,8 +597,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { amount: 1e18, assetSymbol: "WETH", chainId: 1, - collateralAmounts: collateralAmounts, - collateralAssetSymbols: collateralAssetSymbols, + collateralAmounts: repayIntent.collateralAmounts, + collateralAssetSymbols: repayIntent.collateralAssetSymbols, collateralTokenPrices: collateralTokenPrices, collateralTokens: collateralTokens, comet: cometWeth_(1), @@ -611,7 +632,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -620,8 +641,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, - account: address(0xb0b), - nextNonce: 2, + account: address(0xa11ce), + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), @@ -684,6 +705,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -721,6 +744,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, chainPortfolios[1].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -731,6 +756,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -754,6 +781,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[1].nonceSecret, chainPortfolios[1].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -804,7 +833,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC cometPortfolios: cometPortfolios, @@ -869,6 +898,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -878,6 +909,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](0); @@ -928,7 +961,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -938,7 +971,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base cometPortfolios: cometPortfolios, @@ -1006,6 +1039,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -1039,6 +1074,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, chainPortfolios[1].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -1048,6 +1085,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1072,6 +1111,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[1].nonceSecret, chainPortfolios[1].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](0); diff --git a/test/builder/QuarkBuilderCometSupply.t.sol b/test/builder/QuarkBuilderCometSupply.t.sol index 21050653..c04c476e 100644 --- a/test/builder/QuarkBuilderCometSupply.t.sol +++ b/test/builder/QuarkBuilderCometSupply.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -38,6 +38,14 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { internal pure returns (QuarkBuilder.CometSupplyIntent memory) + { + return cometSupply_(chainId, amount, address(0xa11ce)); + } + + function cometSupply_(uint256 chainId, uint256 amount, address sender) + internal + pure + returns (QuarkBuilder.CometSupplyIntent memory) { return QuarkBuilder.CometSupplyIntent({ amount: amount, @@ -45,7 +53,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { blockTimestamp: BLOCK_TIMESTAMP, chainId: chainId, comet: COMET, - sender: address(0xa11ce) + sender: sender }); } @@ -121,6 +129,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -130,6 +140,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -145,7 +157,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { "action context encoded from SupplyActionContext" ); - // // TODO: Check the contents of the EIP712 data + // 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"); @@ -156,7 +168,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](1); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(3e6)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -201,6 +213,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -210,6 +224,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -225,7 +241,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { "action context encoded from SupplyActionContext" ); - // // TODO: Check the contents of the EIP712 data + // 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"); @@ -260,7 +276,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { }); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -297,6 +313,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -306,6 +324,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -321,7 +341,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { "action context encoded from SupplyActionContext" ); - // // TODO: Check the contents of the EIP712 data + // 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"); @@ -362,6 +382,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -371,6 +393,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -395,7 +419,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { function testCometSupplyWithBridge() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.cometSupply( - cometSupply_(8453, 5e6), + // We need to set Bob as the sender because only he has an account on chain 8453 + cometSupply_(8453, 5e6, address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -433,15 +458,17 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -472,6 +499,9 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + // TODO: might need to adjust intent to supply with Bob + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -482,6 +512,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -491,7 +523,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -501,11 +533,13 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "second action is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SUPPLY", "action type is 'SUPPLY'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -530,7 +564,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { function testCometSupplyMaxWithBridge() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.cometSupply( - cometSupply_(8453, type(uint256).max), + // We need to set Bob as the sender because only he has an account on chain 8453 + cometSupply_(8453, type(uint256).max, address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -568,15 +603,17 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -607,6 +644,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -617,6 +656,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -626,7 +667,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -636,11 +677,13 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "second action is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SUPPLY", "action type is 'SUPPLY'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -670,7 +713,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.cometSupply( - cometSupply_(8453, type(uint256).max), + // We need to set Bob as the sender because only he has an account on chain 8453 + cometSupply_(8453, type(uint256).max, address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -699,16 +743,18 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.5e6, // 3e6 - 0.5e6 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -729,6 +775,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -739,6 +787,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -748,7 +798,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -757,11 +807,13 @@ contract QuarkBuilderCometSupplyTest 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SUPPLY", "action type is 'SUPPLY'"); 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"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -791,7 +843,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.cometSupply( - cometSupply_(8453, 5e6), + // We need to set Bob as the sender because only he has an account on chain 8453 + cometSupply_(8453, 5e6, address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -820,16 +873,18 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -850,6 +905,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -860,6 +917,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { 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.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -869,7 +928,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -878,11 +937,13 @@ contract QuarkBuilderCometSupplyTest 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SUPPLY", "action type is 'SUPPLY'"); 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"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( diff --git a/test/builder/QuarkBuilderCometWithdraw.t.sol b/test/builder/QuarkBuilderCometWithdraw.t.sol index 259f258a..77c7282f 100644 --- a/test/builder/QuarkBuilderCometWithdraw.t.sol +++ b/test/builder/QuarkBuilderCometWithdraw.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; @@ -18,13 +18,29 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { pure returns (QuarkBuilder.CometWithdrawIntent memory) { + return cometWithdraw_({ + chainId: chainId, + comet: comet, + assetSymbol: assetSymbol, + amount: amount, + withdrawer: address(0xa11ce) + }); + } + + function cometWithdraw_( + uint256 chainId, + address comet, + string memory assetSymbol, + uint256 amount, + address withdrawer + ) internal pure returns (QuarkBuilder.CometWithdrawIntent memory) { return QuarkBuilder.CometWithdrawIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, chainId: chainId, comet: comet, - withdrawer: address(0xa11ce) + withdrawer: withdrawer }); } @@ -70,6 +86,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -79,6 +97,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -135,6 +155,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -144,6 +166,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -200,6 +224,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -209,6 +235,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { 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 .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -253,7 +281,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](2); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), ALICE_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 3e6), // 3 USDC on mainnet cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -261,7 +289,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), BOB_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 0), // 0 USDC on base cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -269,7 +297,16 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { }); QuarkBuilder.BuilderResult memory result = builder.cometWithdraw( - cometWithdraw_(8453, cometUsdc_(8453), "LINK", 5e18), chainAccountsList, paymentUsdc_(maxCosts) + // We need to set Bob as the withdrawer because only he has an account on chain 8453 + cometWithdraw_({ + chainId: 8453, + comet: cometUsdc_(8453), + assetSymbol: "LINK", + amount: 5e18, + withdrawer: address(0xb0b) + }), + chainAccountsList, + paymentUsdc_(maxCosts) ); address paycallAddress = paycallUsdc_(1); @@ -296,16 +333,18 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -326,6 +365,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -336,6 +377,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -346,7 +389,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { chainId: 1, destinationChainId: 8453, price: USDC_PRICE, - recipient: address(0xa11ce), + recipient: address(0xb0b), token: USDC_1 }) ), @@ -354,11 +397,13 @@ contract QuarkBuilderCometWithdrawTest 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "WITHDRAW", "action type is 'WITHDRAW'"); 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"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -397,7 +442,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: cometPortfolios, @@ -439,6 +484,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -448,6 +495,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { 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 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -486,7 +535,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: cometPortfolios, diff --git a/test/builder/QuarkBuilderHelper.t.sol b/test/builder/QuarkBuilderHelper.t.sol index 9baaffe5..6ed36151 100644 --- a/test/builder/QuarkBuilderHelper.t.sol +++ b/test/builder/QuarkBuilderHelper.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index 2308452b..84810b5c 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -25,12 +25,30 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { uint256 amount, string memory collateralAssetSymbol, uint256 collateralAmount + ) internal pure returns (QuarkBuilder.MorphoBorrowIntent memory) { + return borrowIntent_({ + chainId: chainId, + assetSymbol: assetSymbol, + amount: amount, + collateralAssetSymbol: collateralAssetSymbol, + collateralAmount: collateralAmount, + borrower: address(0xa11ce) + }); + } + + function borrowIntent_( + uint256 chainId, + string memory assetSymbol, + uint256 amount, + string memory collateralAssetSymbol, + uint256 collateralAmount, + address borrower ) internal pure returns (QuarkBuilder.MorphoBorrowIntent memory) { return QuarkBuilder.MorphoBorrowIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, - borrower: address(0xa11ce), + borrower: borrower, chainId: chainId, collateralAmount: collateralAmount, collateralAssetSymbol: collateralAssetSymbol @@ -43,7 +61,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 1e18), cometPortfolios: emptyCometPortfolios_(), @@ -53,7 +71,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -85,7 +103,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 0), // user has 1 WBTC cometPortfolios: emptyCometPortfolios_(), @@ -95,7 +113,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -129,6 +147,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -138,7 +158,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -158,6 +179,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + 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"); @@ -168,7 +190,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -178,7 +200,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xa11ce), - nextNonce: 2, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 10e18, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -229,6 +251,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -238,7 +262,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -258,6 +283,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + 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"); @@ -268,7 +294,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 1e8, 0), // user has 1 WBTC and 1USDC for payment cometPortfolios: emptyCometPortfolios_(), @@ -278,7 +304,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -331,6 +357,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -340,7 +368,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { 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"); - + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -360,6 +389,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + 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"); @@ -370,7 +400,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 0), // user has 1 WBTC but with 0 USDC cometPortfolios: emptyCometPortfolios_(), @@ -380,7 +410,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -433,6 +463,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -442,6 +474,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -461,6 +495,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + 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"); @@ -471,7 +506,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 2, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "cbETH", "WETH"), assetBalances: Arrays.uintArray(5e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -481,7 +516,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e18, 0), cometPortfolios: emptyCometPortfolios_(), @@ -495,7 +530,14 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.morphoBorrow( - borrowIntent_(8453, "WETH", 0.2e18, "cbETH", 1e18), + borrowIntent_({ + chainId: 8453, + assetSymbol: "WETH", + amount: 0.2e18, + collateralAssetSymbol: "cbETH", + collateralAmount: 1e18, + borrower: address(0xb0b) + }), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) ); @@ -525,12 +567,12 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); @@ -541,6 +583,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -571,6 +615,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -581,6 +627,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -590,7 +638,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -599,12 +647,13 @@ 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); 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"); - + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -624,6 +673,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + 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"); @@ -638,7 +688,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 2e8, 1e18), // user does not have enough USDC cometPortfolios: emptyCometPortfolios_(), diff --git a/test/builder/QuarkBuilderMorphoClaimRewards.t.sol b/test/builder/QuarkBuilderMorphoClaimRewards.t.sol index e5adfd8e..876cbe41 100644 --- a/test/builder/QuarkBuilderMorphoClaimRewards.t.sol +++ b/test/builder/QuarkBuilderMorphoClaimRewards.t.sol @@ -104,6 +104,8 @@ contract QuarkBuilderMorphoClaimRewardsTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -113,6 +115,9 @@ contract QuarkBuilderMorphoClaimRewardsTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); + string[] memory assetSymbols = new string[](2); assetSymbols[0] = "USDC"; assetSymbols[1] = "WETH"; @@ -163,7 +168,6 @@ contract QuarkBuilderMorphoClaimRewardsTest is Test, QuarkBuilderTest { paycallUsdc_(1), "script address is correct given the code jar address on mainnet" ); - assertEq( result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( @@ -177,7 +181,6 @@ contract QuarkBuilderMorphoClaimRewardsTest is Test, QuarkBuilderTest { ), "calldata is Paycall.run(MorphoRewardsActions.claimAll(fixtureDistributors, fixtureAccounts, fixtureRewards, fixtureClaimables, fixtureProofs));" ); - assertEq( result.quarkOperations[0].scriptSources[1], abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) @@ -185,6 +188,8 @@ contract QuarkBuilderMorphoClaimRewardsTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -194,6 +199,8 @@ contract QuarkBuilderMorphoClaimRewardsTest is Test, QuarkBuilderTest { 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, 1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); string[] memory assetSymbols = new string[](2); assetSymbols[0] = "USDC"; diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 43762d00..c6ec8a5e 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -25,12 +25,30 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { uint256 amount, string memory collateralAssetSymbol, uint256 collateralAmount + ) internal pure returns (QuarkBuilder.MorphoRepayIntent memory) { + return repayIntent_({ + chainId: chainId, + assetSymbol: assetSymbol, + amount: amount, + collateralAssetSymbol: collateralAssetSymbol, + collateralAmount: collateralAmount, + repayer: address(0xa11ce) + }); + } + + function repayIntent_( + uint256 chainId, + string memory assetSymbol, + uint256 amount, + string memory collateralAssetSymbol, + uint256 collateralAmount, + address repayer ) internal pure returns (QuarkBuilder.MorphoRepayIntent memory) { return QuarkBuilder.MorphoRepayIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, - repayer: address(0xa11ce), + repayer: repayer, chainId: chainId, collateralAmount: collateralAmount, collateralAssetSymbol: collateralAssetSymbol @@ -58,7 +76,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 8453, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 0, 1e18), // user does not have enough USDC cometPortfolios: emptyCometPortfolios_(), @@ -80,7 +98,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC cometPortfolios: emptyCometPortfolios_(), @@ -90,7 +108,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -112,12 +130,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); assertEq(result.paymentCurrency, "usd", "usd currency"); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one operation"); assertEq( result.quarkOperations[0].scriptAddress, - MorphoActionsAddress, + morphoActionsAddress, "script address is correct given the code jar address on mainnet" ); assertEq( @@ -133,6 +151,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -142,7 +162,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -162,6 +183,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + 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"); @@ -172,7 +194,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "ETH", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -182,7 +204,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xa11ce), - nextNonce: 2, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "ETH", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 1e18, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -207,7 +229,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address multicallAddress = CodeJarHelper.getCodeAddress(type(Multicall).creationCode); address wrapperActionsAddress = CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one merged operation"); @@ -218,7 +240,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); address[] memory callContracts = new address[](2); callContracts[0] = wrapperActionsAddress; - callContracts[1] = MorphoActionsAddress; + callContracts[1] = morphoActionsAddress; bytes[] memory callDatas = new bytes[](2); callDatas[0] = abi.encodeWithSelector( WrapperActions.wrapETH.selector, TokenWrapper.getKnownWrapperTokenPair(8453, "WETH").wrapper, 1e18 @@ -231,7 +253,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { 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(8453), 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); @@ -240,6 +262,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -249,7 +273,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -269,6 +294,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + 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"); @@ -279,7 +305,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(2e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -289,7 +315,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -313,7 +339,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { paymentUsdc_(maxCosts) // and paying with USDC ); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); address paycallAddress = paycallUsdc_(1); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -329,7 +355,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - MorphoActionsAddress, + morphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0e8) @@ -347,6 +373,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -356,6 +384,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralAmounts = new uint256[](1); collateralAmounts[0] = 0e18; @@ -385,6 +415,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + 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"); @@ -401,7 +432,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -411,7 +442,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), @@ -425,7 +456,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { "USDC", // repaying 2 USDC, bridged from mainnet to base 2e6, "WETH", - 0e18 + 0e18, + address(0xb0b) ), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) @@ -434,7 +466,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address paycallAddress = paycallUsdc_(1); address paycallAddressBase = paycallUsdc_(8453); address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -456,12 +488,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.2e6, // 2e6 repaid + 0.2e6 max cost on Base 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 2.2e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 2.2e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); @@ -472,6 +504,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -484,7 +518,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - MorphoActionsAddress, + morphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 2e6, 0e18) @@ -502,6 +536,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -512,6 +548,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -522,7 +560,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainId: 1, destinationChainId: 8453, price: USDC_PRICE, - recipient: address(0xa11ce), + recipient: address(0xb0b), token: usdc_(1) }) ), @@ -530,12 +568,13 @@ 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); 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"); - + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -555,6 +594,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + 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"); @@ -577,7 +617,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(20e6, 0, 0, 0), // has 20 USDC cometPortfolios: emptyCometPortfolios_(), @@ -603,7 +643,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 MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one operation"); @@ -617,7 +657,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - MorphoActionsAddress, + morphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, ( @@ -641,6 +681,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -650,7 +692,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -670,6 +713,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + 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"); @@ -693,7 +737,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC cometPortfolios: emptyCometPortfolios_(), @@ -702,8 +746,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, - account: address(0xa11ce), - nextNonce: 12, + account: address(0xb0b), + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base cometPortfolios: emptyCometPortfolios_(), @@ -718,7 +762,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { "USDC", type(uint256).max, // repaying max (all 10 USDC) "WETH", - 0 + 0, + address(0xb0b) ), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) @@ -727,7 +772,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.paymentCurrency, "usdc", "usdc currency"); address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).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)) ); @@ -753,12 +798,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 10.11e6, // 10e6 repaid + .1% buffer + 0.1e6 max cost on Base 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 10.11e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 10.11e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); @@ -769,6 +814,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -780,7 +827,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - MorphoActionsAddress, + morphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, ( @@ -804,6 +851,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -813,6 +862,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -823,7 +874,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainId: 1, destinationChainId: 8453, price: USDC_PRICE, - recipient: address(0xa11ce), + recipient: address(0xb0b), token: USDC_1 }) ), @@ -832,12 +883,13 @@ 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); 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"); - + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -857,6 +909,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + 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/QuarkBuilderMorphoVaultSupply.t.sol b/test/builder/QuarkBuilderMorphoVaultSupply.t.sol index befdd1c6..fc59016e 100644 --- a/test/builder/QuarkBuilderMorphoVaultSupply.t.sol +++ b/test/builder/QuarkBuilderMorphoVaultSupply.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -23,13 +23,22 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { internal pure returns (QuarkBuilder.MorphoVaultSupplyIntent memory) + { + return + morphoSupplyIntent_({chainId: chainId, amount: amount, assetSymbol: assetSymbol, sender: address(0xa11ce)}); + } + + function morphoSupplyIntent_(uint256 chainId, uint256 amount, string memory assetSymbol, address sender) + internal + pure + returns (QuarkBuilder.MorphoVaultSupplyIntent memory) { return QuarkBuilder.MorphoVaultSupplyIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, chainId: chainId, - sender: address(0xa11ce) + sender: sender }); } @@ -71,7 +80,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](3); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 0e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -79,7 +88,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 0e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -87,12 +96,13 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, - quarkStates: quarkStates_(address(0xc0b), 5), + quarkSecrets: quarkSecrets_(address(0xc0b), bytes32(uint256(5))), assetPositionsList: assetPositionsList_(7777, address(0xc0b), 100e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), morphoVaultPositions: emptyMorphoVaultPositions_() }); + vm.expectRevert(abi.encodeWithSelector(QuarkBuilder.FundsUnavailable.selector, "USDC", 2e6, 0)); builder.morphoVaultSupply( // there is no bridge to brige from 7777, so we cannot get to our funds @@ -139,6 +149,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -148,6 +160,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -174,7 +188,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](1); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(3e6)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -210,6 +224,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -219,6 +235,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -268,7 +286,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { }); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -306,6 +324,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -315,6 +335,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -373,6 +395,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -382,6 +406,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -406,7 +432,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { function testMorphoVaultSupplyWithBridge() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.morphoVaultSupply( - morphoSupplyIntent_(8453, 5e6, "USDC"), + morphoSupplyIntent_(8453, 5e6, "USDC", address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -429,15 +455,17 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -455,6 +483,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -465,6 +495,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -474,7 +506,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -484,11 +516,13 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "second action is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_SUPPLY", "action type is 'MORPHO_VAULT_SUPPLY'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -513,7 +547,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { function testMorphoVaultSupplyMaxWithBridge() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.morphoVaultSupply( - morphoSupplyIntent_(8453, type(uint256).max, "USDC"), + morphoSupplyIntent_(8453, type(uint256).max, "USDC", address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -536,15 +570,17 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -562,6 +598,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -572,6 +610,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -581,7 +621,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -591,11 +631,13 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "second action is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_SUPPLY", "action type is 'MORPHO_VAULT_SUPPLY'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -625,7 +667,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.morphoVaultSupply( - morphoSupplyIntent_(8453, type(uint256).max, "USDC"), + morphoSupplyIntent_(8453, type(uint256).max, "USDC", address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -654,16 +696,18 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.5e6, // 3e6 - 0.5e6 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -686,6 +730,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -696,6 +742,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -705,7 +753,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -714,11 +762,13 @@ contract QuarkBuilderMorphoVaultTest 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_SUPPLY", "action type is 'MORPHO_VAULT_SUPPLY'"); assertEq(result.actions[1].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_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"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -748,7 +798,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.morphoVaultSupply( - morphoSupplyIntent_(8453, 5e6, "USDC"), + morphoSupplyIntent_(8453, 5e6, "USDC", address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -777,16 +827,18 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -809,6 +861,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -819,6 +873,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { 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.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -828,7 +884,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -837,11 +893,13 @@ contract QuarkBuilderMorphoVaultTest 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_SUPPLY", "action type is 'MORPHO_VAULT_SUPPLY'"); 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"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( diff --git a/test/builder/QuarkBuilderMorphoVaultWithdraw.t.sol b/test/builder/QuarkBuilderMorphoVaultWithdraw.t.sol index ec60d9cd..23eb6a57 100644 --- a/test/builder/QuarkBuilderMorphoVaultWithdraw.t.sol +++ b/test/builder/QuarkBuilderMorphoVaultWithdraw.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; @@ -19,13 +19,26 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { internal pure returns (QuarkBuilder.MorphoVaultWithdrawIntent memory) + { + return morphoWithdrawIntent_({ + amount: amount, + assetSymbol: assetSymbol, + chainId: chainId, + withdrawer: address(0xa11ce) + }); + } + + function morphoWithdrawIntent_(uint256 chainId, uint256 amount, string memory assetSymbol, address withdrawer) + internal + pure + returns (QuarkBuilder.MorphoVaultWithdrawIntent memory) { return QuarkBuilder.MorphoVaultWithdrawIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, chainId: chainId, - withdrawer: address(0xa11ce) + withdrawer: withdrawer }); } @@ -56,6 +69,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -65,6 +80,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -121,6 +138,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -130,6 +149,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -186,6 +207,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -195,6 +218,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { 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"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -239,7 +264,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](2); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), ALICE_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 3e6), // 3 USDC on mainnet cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -247,7 +272,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), BOB_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 0), // 0 USDC on base cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -255,7 +280,9 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { }); QuarkBuilder.BuilderResult memory result = builder.morphoVaultWithdraw( - morphoWithdrawIntent_(8453, 1e18, "WETH"), chainAccountsList, paymentUsdc_(maxCosts) + morphoWithdrawIntent_({chainId: 8453, amount: 1e18, assetSymbol: "WETH", withdrawer: address(0xb0b)}), + chainAccountsList, + paymentUsdc_(maxCosts) ); address paycallAddress = paycallUsdc_(1); @@ -282,16 +309,18 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -312,6 +341,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -322,6 +353,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -332,7 +365,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { chainId: 1, destinationChainId: 8453, price: USDC_PRICE, - recipient: address(0xa11ce), + recipient: address(0xb0b), token: USDC_1 }) ), @@ -340,11 +373,13 @@ contract QuarkBuilderMorphoVaultWithdrawTest 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_WITHDRAW", "action type is 'MORPHO_VAULT_WITHDRAW'"); 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"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -381,7 +416,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -423,6 +458,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -432,6 +469,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { 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 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -468,7 +507,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), diff --git a/test/builder/QuarkBuilderRecurringSwap.t.sol b/test/builder/QuarkBuilderRecurringSwap.t.sol index b918efd1..0f720506 100644 --- a/test/builder/QuarkBuilderRecurringSwap.t.sol +++ b/test/builder/QuarkBuilderRecurringSwap.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; import {QuarkBuilderTest} from "test/builder/lib/QuarkBuilderTest.sol"; +import {ReplayableHelper} from "test/builder/lib/ReplayableHelper.sol"; import {RecurringSwap} from "src/RecurringSwap.sol"; import {CCTPBridgeActions} from "src/BridgeScripts.sol"; @@ -190,7 +191,7 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { buyAmount: 1e18, isExactOut: false, interval: 86_400, - sender: address(0xfe11a), + sender: address(0xa11ce), blockTimestamp: BLOCK_TIMESTAMP }); QuarkBuilder.BuilderResult memory result = builder.recurringSwap( @@ -229,15 +230,23 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { "calldata is RecurringSwap.swap(SwapConfig(...));" ); assertEq(result.quarkOperations[0].expiry, type(uint256).max, "expiry is type(uint256).max"); + assertEq( + result.quarkOperations[0].nonce, + ReplayableHelper.generateNonceFromSecret(ALICE_DEFAULT_SECRET, Actions.RECURRING_SWAP_TOTAL_PLAYS), + "unexpected nonce" + ); + assertEq(result.quarkOperations[0].isReplayable, true, "isReplayable is false"); // 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(0xfe11a), "0xfe11a does the swap"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce does the swap"); assertEq(result.actions[0].actionType, "RECURRING_SWAP", "action type is 'RECURRING_SWAP'"); 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, Actions.RECURRING_SWAP_TOTAL_PLAYS, "total plays is correct"); assertEq( result.actions[0].actionContext, abi.encode( @@ -273,7 +282,7 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { buyAmount: 1e18, isExactOut: true, interval: 86_400, - sender: address(0xfe11a), + sender: address(0xa11ce), blockTimestamp: BLOCK_TIMESTAMP }); QuarkBuilder.BuilderResult memory result = builder.recurringSwap( @@ -312,15 +321,23 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { "calldata is RecurringSwap.swap(SwapConfig(...));" ); assertEq(result.quarkOperations[0].expiry, type(uint256).max, "expiry is type(uint256).max"); + assertEq( + result.quarkOperations[0].nonce, + ReplayableHelper.generateNonceFromSecret(ALICE_DEFAULT_SECRET, Actions.RECURRING_SWAP_TOTAL_PLAYS), + "unexpected nonce" + ); + assertEq(result.quarkOperations[0].isReplayable, true, "isReplayable is false"); // 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(0xfe11a), "0xfe11a does the swap"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce does the swap"); assertEq(result.actions[0].actionType, "RECURRING_SWAP", "action type is 'RECURRING_SWAP'"); 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, Actions.RECURRING_SWAP_TOTAL_PLAYS, "total plays is correct"); assertEq( result.actions[0].actionContext, abi.encode( @@ -356,7 +373,7 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { buyAmount: 1e18, isExactOut: false, interval: 86_400, - sender: address(0xfe11a), + sender: address(0xa11ce), blockTimestamp: BLOCK_TIMESTAMP }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -392,15 +409,23 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { "calldata is Paycall.run(RecurringSwap.swap(SwapConfig(...)), 5e6);" ); assertEq(result.quarkOperations[0].expiry, type(uint256).max, "expiry is type(uint256).max"); + assertEq( + result.quarkOperations[0].nonce, + ReplayableHelper.generateNonceFromSecret(ALICE_DEFAULT_SECRET, Actions.RECURRING_SWAP_TOTAL_PLAYS), + "unexpected nonce" + ); + assertEq(result.quarkOperations[0].isReplayable, true, "isReplayable is false"); // 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(0xfe11a), "0xfe11a does the swap"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce does the swap"); assertEq(result.actions[0].actionType, "RECURRING_SWAP", "action type is 'RECURRING_SWAP'"); 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, 5e6, "payment max is set to 5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, Actions.RECURRING_SWAP_TOTAL_PLAYS, "total plays is correct"); assertEq( result.actions[0].actionContext, abi.encode( diff --git a/test/builder/QuarkBuilderSwap.t.sol b/test/builder/QuarkBuilderSwap.t.sol index a980258d..e36b9c5e 100644 --- a/test/builder/QuarkBuilderSwap.t.sol +++ b/test/builder/QuarkBuilderSwap.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -181,6 +181,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -190,6 +192,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -248,7 +252,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { }); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -288,6 +292,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -297,6 +303,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -365,6 +373,9 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); @@ -373,6 +384,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { 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, 5e6, "payment max is set to 5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -409,7 +422,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](3); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(9005e6)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -417,7 +430,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(8453, address(0xb0b), uint256(0)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -425,7 +438,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, - quarkStates: quarkStates_(address(0xc0b), 5), + quarkSecrets: quarkSecrets_(address(0xc0b), bytes32(uint256(5))), assetPositionsList: assetPositionsList_(7777, address(0xc0b), uint256(0)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -467,6 +480,9 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); @@ -475,6 +491,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 5e6, "payment max is set to 5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -507,7 +525,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { function testBridgeSwapSucceeds() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.swap( - buyWeth_(8453, usdc_(8453), 3000e6, 1e18, address(0xa11ce), BLOCK_TIMESTAMP), // swap 3000 USDC on chain 8453 to 1 WETH + buyWeth_(8453, usdc_(8453), 3000e6, 1e18, address(0xb0b), BLOCK_TIMESTAMP), // swap 3000 USDC on chain 8453 to 1 WETH chainAccountsList_(4000e6), // holding 4000 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -544,15 +562,18 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1000e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, address( @@ -583,6 +604,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -592,6 +615,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -601,7 +626,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -609,11 +634,13 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SWAP", "action type is 'SWAP'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -650,7 +677,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 1e6}); // Note: There are 2000e6 USDC on each chain, so the Builder should attempt to bridge 1000 + 1 (for payment) USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.swap( - buyWeth_(8453, usdc_(8453), 3000e6, 1e18, address(0xa11ce), BLOCK_TIMESTAMP), // swap 3000 USDC on chain 8453 to 1 WETH + buyWeth_(8453, usdc_(8453), 3000e6, 1e18, address(0xb0b), BLOCK_TIMESTAMP), // swap 3000 USDC on chain 8453 to 1 WETH chainAccountsList_(4000e6), // holding 4000 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -683,16 +710,19 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1001e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 5e5);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e5);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, paycallAddressBase, @@ -719,6 +749,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -728,6 +760,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { 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, 5e6, "payment should have max cost of 5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -737,7 +771,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -745,11 +779,13 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SWAP", "action type is 'SWAP'"); 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"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -785,7 +821,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 5e6}); maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 1e6}); QuarkBuilder.BuilderResult memory result = builder.swap( - buyWeth_(8453, usdc_(8453), type(uint256).max, 2e18, address(0xa11ce), BLOCK_TIMESTAMP), // swap max on chain 8453 to 4 WETH + buyWeth_(8453, usdc_(8453), type(uint256).max, 2e18, address(0xb0b), BLOCK_TIMESTAMP), // swap max on chain 8453 to 4 WETH chainAccountsList_(6010e6), // holding 6010 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -818,16 +854,19 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3000e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 5e6 ), - "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3000e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 5e5);" + "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3000e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e5);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, quotecallAddressBase, @@ -854,6 +893,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -863,6 +904,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 5e6, "payment should have max cost of 5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -872,7 +915,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -880,11 +923,13 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SWAP", "action type is 'SWAP'"); assertEq(result.actions[1].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_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"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -921,7 +966,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 3500e6}); // Note: There are 3000e6 USDC on each chain, so the Builder should attempt to bridge 500 USDC to chain 8453 to cover the max cost QuarkBuilder.BuilderResult memory result = builder.swap( - buyWeth_(8453, usdt_(8453), 3000e6, 1e18, address(0xa11ce), BLOCK_TIMESTAMP), // swap 3000 USDT on chain 8453 to 1 WETH + buyWeth_(8453, usdt_(8453), 3000e6, 1e18, address(0xb0b), BLOCK_TIMESTAMP), // swap 3000 USDT on chain 8453 to 1 WETH chainAccountsList_(6000e6), // holding 6000 USDC and USDT in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -954,16 +999,19 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 500e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 500e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 5e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 500e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( @@ -990,6 +1038,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -999,6 +1049,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { 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, 5e6, "payment should have max cost of 5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1008,7 +1060,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -1016,11 +1068,13 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SWAP", "action type is 'SWAP'"); 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, 3500e6, "payment should have max cost of 3500e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( diff --git a/test/builder/QuarkBuilderTransfer.t.sol b/test/builder/QuarkBuilderTransfer.t.sol index c6b63c47..59ea4b60 100644 --- a/test/builder/QuarkBuilderTransfer.t.sol +++ b/test/builder/QuarkBuilderTransfer.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity ^0.8.23; +pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol"; @@ -52,7 +52,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address recipient, uint256 blockTimestamp ) internal pure returns (QuarkBuilder.TransferIntent memory) { - return QuarkBuilder.TransferIntent({ + return transferToken_({ chainId: chainId, sender: address(0xa11ce), recipient: recipient, @@ -62,6 +62,24 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); } + function transferToken_( + string memory assetSymbol, + uint256 chainId, + uint256 amount, + address sender, + address recipient, + uint256 blockTimestamp + ) internal pure returns (QuarkBuilder.TransferIntent memory) { + return QuarkBuilder.TransferIntent({ + chainId: chainId, + sender: sender, + recipient: recipient, + amount: amount, + assetSymbol: assetSymbol, + blockTimestamp: blockTimestamp + }); + } + function testInsufficientFunds() public { QuarkBuilder builder = new QuarkBuilder(); vm.expectRevert(abi.encodeWithSelector(QuarkBuilder.FundsUnavailable.selector, "USDC", 10e6, 0e6)); @@ -147,6 +165,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -156,6 +176,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -212,6 +234,9 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); @@ -220,6 +245,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { 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, 1e5, "payment max is set to 1e5 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -245,7 +272,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { QuarkBuilder builder = new QuarkBuilder(); // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.transfer( - transferUsdc_(8453, 5e6, address(0xceecee), BLOCK_TIMESTAMP), // transfer 5 USDC on chain 8453 to 0xceecee + transferToken_({ + assetSymbol: "USDC", + chainId: 8453, + amount: 5e6, + sender: address(0xb0b), + recipient: address(0xceecee), + blockTimestamp: BLOCK_TIMESTAMP + }), // transfer 5 USDC on chain 8453 to 0xceecee chainAccountsList_(6e6), // holding 6 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -282,15 +316,18 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, address( @@ -319,6 +356,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -328,6 +367,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -337,7 +378,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -345,11 +386,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -379,7 +422,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.transfer( - transferUsdc_(8453, 5e6, address(0xceecee), BLOCK_TIMESTAMP), // transfer 5 USDC on chain 8453 to 0xceecee + transferToken_({ + assetSymbol: "USDC", + chainId: 8453, + amount: 5e6, + sender: address(0xb0b), + recipient: address(0xceecee), + blockTimestamp: BLOCK_TIMESTAMP + }), // transfer 5 USDC on chain 8453 to 0xceecee chainAccountsList_(6e6), // holding 6 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -406,16 +456,19 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 5e5);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e5);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, paycallAddressBase, @@ -434,6 +487,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -443,6 +498,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { 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.5e6, "payment should have max cost of 5e5"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -452,7 +509,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -460,11 +517,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'"); 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 1e5"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -494,7 +553,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 1.5 USDC to chain 8453 to pay for the txn QuarkBuilder.BuilderResult memory result = builder.transfer( - transferToken_("USDT", 8453, 3e6, address(0xceecee), BLOCK_TIMESTAMP), // transfer 3 USDT on chain 8453 to 0xceecee + transferToken_({ + assetSymbol: "USDT", + chainId: 8453, + amount: 3e6, + sender: address(0xb0b), + recipient: address(0xceecee), + blockTimestamp: BLOCK_TIMESTAMP + }), // transfer 3 USDT on chain 8453 to 0xceecee chainAccountsList_(6e6), // holding 6 USDC and USDT in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -521,16 +587,19 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1.5e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1.5e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1.5e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, paycallAddressBase, @@ -549,6 +618,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -558,6 +629,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { 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.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -567,7 +640,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -575,11 +648,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'"); 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, 4.5e6, "payment should have max cost of 4.5e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -607,7 +682,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](1); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(10e6)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -648,6 +723,9 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); @@ -656,6 +734,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 1e5, "payment max is set to 1e5 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -685,7 +765,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](2); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 8e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -693,7 +773,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 4e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -701,7 +781,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); QuarkBuilder.BuilderResult memory result = builder.transfer( - transferUsdc_(8453, type(uint256).max, address(0xceecee), BLOCK_TIMESTAMP), // transfer max USDC on chain 8453 to 0xceecee + transferToken_({ + assetSymbol: "USDC", + chainId: 8453, + amount: type(uint256).max, + sender: address(0xb0b), + recipient: address(0xceecee), + blockTimestamp: BLOCK_TIMESTAMP + }), // transfer max USDC on chain 8453 to 0xceecee chainAccountsList, // holding 8 USDC on chains 1, and 4 USDC on 8453 paymentUsdc_(maxCosts) ); @@ -732,16 +819,19 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 7.5e6, // 8e6 (holdings on mainnet) - 0.5e6 (max cost on mainnet) 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Quote.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 7.5e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Quote.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 7.5e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, quotecallAddressBase, @@ -763,6 +853,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -772,6 +864,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 5e5"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -781,7 +875,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -789,11 +883,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); 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].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[1].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_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 1e5"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -824,7 +920,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](3); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 8e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -832,7 +928,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 4e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -840,7 +936,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, - quarkStates: quarkStates_(address(0xfe11a), 2), + quarkSecrets: quarkSecrets_(address(0xfe11a), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(7777, address(0xfe11a), 5e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -929,7 +1025,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -971,6 +1067,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "1 action"); @@ -979,6 +1077,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is USD"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1032,7 +1132,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -1082,6 +1182,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "1 action"); @@ -1090,6 +1192,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'"); 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].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1143,7 +1247,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -1194,6 +1298,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "1 action"); @@ -1202,6 +1308,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1255,7 +1363,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -1297,6 +1405,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "1 action"); @@ -1305,6 +1415,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is USD"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( diff --git a/test/builder/lib/QuarkBuilderTest.sol b/test/builder/lib/QuarkBuilderTest.sol index 863b6f72..7a3cc402 100644 --- a/test/builder/lib/QuarkBuilderTest.sol +++ b/test/builder/lib/QuarkBuilderTest.sol @@ -55,6 +55,10 @@ contract QuarkBuilderTest { address constant ETH_USD_PRICE_FEED_1 = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; address constant ETH_USD_PRICE_FEED_8453 = 0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70; + bytes32 constant ALICE_DEFAULT_SECRET = bytes32(uint256(12)); + bytes32 constant BOB_DEFAULT_SECRET = bytes32(uint256(2)); + bytes32 constant COB_DEFAULT_SECRET = bytes32(uint256(5)); + /** * * Fixture Functions @@ -92,7 +96,7 @@ contract QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](3); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), ALICE_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(amount / 2)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -100,7 +104,7 @@ contract QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), BOB_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(8453, address(0xb0b), uint256(amount / 2)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -108,7 +112,7 @@ contract QuarkBuilderTest { }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, - quarkStates: quarkStates_(address(0xc0b), 5), + quarkSecrets: quarkSecrets_(address(0xc0b), COB_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(7777, address(0xc0b), uint256(0)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -132,10 +136,10 @@ contract QuarkBuilderTest { return emptyMorphoVaultPositions; } - function quarkStates_() internal pure returns (Accounts.QuarkState[] memory) { - Accounts.QuarkState[] memory quarkStates = new Accounts.QuarkState[](1); - quarkStates[0] = quarkState_(); - return quarkStates; + function quarkSecrets_() internal pure returns (Accounts.QuarkSecret[] memory) { + Accounts.QuarkSecret[] memory quarkSecrets = new Accounts.QuarkSecret[](1); + quarkSecrets[0] = quarkSecret_(); + return quarkSecrets; } function maxCosts_(uint256 chainId, uint256 amount) internal pure returns (PaymentInfo.PaymentMaxCost[] memory) { @@ -285,24 +289,28 @@ contract QuarkBuilderTest { } } - function quarkStates_(address account, uint96 nextNonce) internal pure returns (Accounts.QuarkState[] memory) { - Accounts.QuarkState[] memory quarkStates = new Accounts.QuarkState[](1); - quarkStates[0] = quarkState_(account, nextNonce); - return quarkStates; + function quarkSecrets_(address account, bytes32 nonceSecret) + internal + pure + returns (Accounts.QuarkSecret[] memory) + { + Accounts.QuarkSecret[] memory quarkSecrets = new Accounts.QuarkSecret[](1); + quarkSecrets[0] = quarkSecret_(account, nonceSecret); + return quarkSecrets; } - function quarkState_() internal pure returns (Accounts.QuarkState memory) { - return quarkState_(address(0xa11ce), 3); + function quarkSecret_() internal pure returns (Accounts.QuarkSecret memory) { + return quarkSecret_(address(0xa11ce), bytes32(uint256(3))); } - function quarkState_(address account, uint96 nextNonce) internal pure returns (Accounts.QuarkState memory) { - return Accounts.QuarkState({account: account, quarkNextNonce: nextNonce}); + function quarkSecret_(address account, bytes32 nonceSecret) internal pure returns (Accounts.QuarkSecret memory) { + return Accounts.QuarkSecret({account: account, nonceSecret: nonceSecret}); } struct ChainPortfolio { uint256 chainId; address account; - uint96 nextNonce; + bytes32 nonceSecret; string[] assetSymbols; uint256[] assetBalances; CometPortfolio[] cometPortfolios; @@ -356,7 +364,7 @@ contract QuarkBuilderTest { for (uint256 i = 0; i < chainPortfolios.length; ++i) { chainAccountsList[i] = Accounts.ChainAccounts({ chainId: chainPortfolios[i].chainId, - quarkStates: quarkStates_(chainPortfolios[i].account, chainPortfolios[i].nextNonce), + quarkSecrets: quarkSecrets_(chainPortfolios[i].account, chainPortfolios[i].nonceSecret), assetPositionsList: assetPositionsForAssets( chainPortfolios[i].chainId, chainPortfolios[i].account, diff --git a/test/builder/lib/ReplayableHelper.sol b/test/builder/lib/ReplayableHelper.sol new file mode 100644 index 00000000..7fe07d71 --- /dev/null +++ b/test/builder/lib/ReplayableHelper.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.27; + +library ReplayableHelper { + function generateNonceFromSecret(bytes32 secret, uint256 totalPlays) internal pure returns (bytes32) { + uint256 replayCount = totalPlays - 1; + for (uint256 i = 0; i < replayCount; ++i) { + secret = keccak256(abi.encodePacked(secret)); + } + return secret; + } +} diff --git a/test/lib/AllowCallbacks.sol b/test/lib/AllowCallbacks.sol index 8af9c5d1..296b5c87 100644 --- a/test/lib/AllowCallbacks.sol +++ b/test/lib/AllowCallbacks.sol @@ -1,18 +1,15 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "quark-core/src/QuarkScript.sol"; import "quark-core/src/QuarkWallet.sol"; contract AllowCallbacks is QuarkScript { function run(address callbackAddress) public { - QuarkWallet self = QuarkWallet(payable(address(this))); - self.stateManager().write(self.CALLBACK_KEY(), bytes32(uint256(uint160(callbackAddress)))); - } - - function allowCallbackAndReplay() public { - allowCallback(); - allowReplay(); + bytes32 callbackSlot = QuarkWalletMetadata.CALLBACK_SLOT; + assembly { + tstore(callbackSlot, callbackAddress) + } } function clear() public { diff --git a/test/lib/CallcodeReentrancy.sol b/test/lib/CallcodeReentrancy.sol index 594718c3..93468649 100644 --- a/test/lib/CallcodeReentrancy.sol +++ b/test/lib/CallcodeReentrancy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/console.sol"; diff --git a/test/lib/Counter.sol b/test/lib/Counter.sol index 65a47172..d9b02cb9 100644 --- a/test/lib/Counter.sol +++ b/test/lib/Counter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; interface HasCallback { function callback() external payable; diff --git a/test/lib/CounterScript.sol b/test/lib/CounterScript.sol index 773b1433..deca9d4f 100644 --- a/test/lib/CounterScript.sol +++ b/test/lib/CounterScript.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "test/lib/Counter.sol"; diff --git a/test/lib/EvilReceiver.sol b/test/lib/EvilReceiver.sol index b0d9461d..1a0e4f4a 100644 --- a/test/lib/EvilReceiver.sol +++ b/test/lib/EvilReceiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "quark-core/src/QuarkWallet.sol"; import "quark-core/src/QuarkScript.sol"; diff --git a/test/lib/IStETH.sol b/test/lib/IStETH.sol index 4735b099..a58928d7 100644 --- a/test/lib/IStETH.sol +++ b/test/lib/IStETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; interface IStETH { function submit(address _referral) external payable returns (uint256); diff --git a/test/lib/Permit2Helper.sol b/test/lib/Permit2Helper.sol index ac3f88d2..9fb952de 100644 --- a/test/lib/Permit2Helper.sol +++ b/test/lib/Permit2Helper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; interface Permit2 { function permit(address owner, Permit2Helper.PermitSingle memory permitSingle, bytes calldata signature) external; diff --git a/test/lib/QuarkOperationHelper.sol b/test/lib/QuarkOperationHelper.sol index f06649b2..99d5ae81 100644 --- a/test/lib/QuarkOperationHelper.sol +++ b/test/lib/QuarkOperationHelper.sol @@ -1,9 +1,12 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; + import "quark-core/src/QuarkWallet.sol"; +import {YulHelper} from "test/lib/YulHelper.sol"; + enum ScriptType { ScriptAddress, ScriptSource @@ -12,6 +15,9 @@ enum ScriptType { // TODO: QuarkOperationHelper ScriptType doesn't really make sense anymore, since scriptSource // has been replaced with scriptSources and scriptAddress is now always required. contract QuarkOperationHelper is Test { + error SemiRandomNonceRequiresQuarkNonceManagerOrInitializedQuarkWallet(address quarkWallet); + error Impossible(); + function newBasicOp(QuarkWallet wallet, bytes memory scriptSource, ScriptType scriptType) external returns (QuarkWallet.QuarkOperation memory) @@ -34,6 +40,29 @@ contract QuarkOperationHelper is Test { bytes memory scriptCalldata, bytes[] memory ensureScripts, ScriptType scriptType + ) public returns (QuarkWallet.QuarkOperation memory) { + return newBasicOpWithCalldata( + wallet, scriptSource, scriptCalldata, ensureScripts, scriptType, semiRandomNonce(wallet) + ); + } + + function newBasicOpWithCalldata( + QuarkWallet wallet, + bytes memory scriptSource, + bytes memory scriptCalldata, + ScriptType scriptType, + bytes32 nonce + ) public returns (QuarkWallet.QuarkOperation memory) { + return newBasicOpWithCalldata(wallet, scriptSource, scriptCalldata, new bytes[](0), scriptType, nonce); + } + + function newBasicOpWithCalldata( + QuarkWallet wallet, + bytes memory scriptSource, + bytes memory scriptCalldata, + bytes[] memory ensureScripts, + ScriptType scriptType, + bytes32 nonce ) public returns (QuarkWallet.QuarkOperation memory) { address scriptAddress = wallet.codeJar().saveCode(scriptSource); if (scriptType == ScriptType.ScriptAddress) { @@ -41,7 +70,8 @@ contract QuarkOperationHelper is Test { scriptAddress: scriptAddress, scriptSources: ensureScripts, scriptCalldata: scriptCalldata, - nonce: wallet.stateManager().nextNonce(address(wallet)), + nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); } else { @@ -49,9 +79,128 @@ contract QuarkOperationHelper is Test { scriptAddress: scriptAddress, scriptSources: ensureScripts, scriptCalldata: scriptCalldata, - nonce: wallet.stateManager().nextNonce(address(wallet)), + nonce: nonce, + isReplayable: false, expiry: block.timestamp + 1000 }); } } + + function newReplayableOpWithCalldata( + QuarkWallet wallet, + bytes memory scriptSource, + bytes memory scriptCalldata, + ScriptType scriptType, + uint256 replays + ) public returns (QuarkWallet.QuarkOperation memory, bytes32[] memory submissionTokens) { + return newReplayableOpWithCalldata(wallet, scriptSource, scriptCalldata, new bytes[](0), scriptType, replays); + } + + function newReplayableOpWithCalldata( + QuarkWallet wallet, + bytes memory scriptSource, + bytes memory scriptCalldata, + ScriptType scriptType, + uint256 replays, + bytes32 nonce + ) public returns (QuarkWallet.QuarkOperation memory, bytes32[] memory submissionTokens) { + return newReplayableOpWithCalldata( + wallet, scriptSource, scriptCalldata, new bytes[](0), scriptType, replays, nonce + ); + } + + function newReplayableOpWithCalldata( + QuarkWallet wallet, + bytes memory scriptSource, + bytes memory scriptCalldata, + bytes[] memory ensureScripts, + ScriptType scriptType, + uint256 replays + ) public returns (QuarkWallet.QuarkOperation memory, bytes32[] memory submissionTokens) { + return newReplayableOpWithCalldata( + wallet, scriptSource, scriptCalldata, ensureScripts, scriptType, replays, semiRandomNonce(wallet) + ); + } + + function newReplayableOpWithCalldata( + QuarkWallet wallet, + bytes memory scriptSource, + bytes memory scriptCalldata, + bytes[] memory ensureScripts, + ScriptType scriptType, + uint256 replays, + bytes32 nonce + ) public returns (QuarkWallet.QuarkOperation memory, bytes32[] memory submissionTokens) { + QuarkWallet.QuarkOperation memory operation = + newBasicOpWithCalldata(wallet, scriptSource, scriptCalldata, ensureScripts, scriptType, nonce); + submissionTokens = new bytes32[](replays + 1); + submissionTokens[replays] = nonce; + for (uint256 i = 0; i < replays; i++) { + nonce = keccak256(abi.encodePacked(nonce)); + submissionTokens[replays - i - 1] = nonce; + } + operation.nonce = nonce; + operation.isReplayable = true; + return (operation, submissionTokens); + } + + function cancelReplayableByNop(QuarkWallet wallet, QuarkWallet.QuarkOperation memory quarkOperation) + public + returns (QuarkWallet.QuarkOperation memory) + { + return getCancelOperation(wallet, quarkOperation.nonce, abi.encodeWithSignature("nop()")); + } + + function cancelReplayableByNewOp(QuarkWallet wallet, QuarkWallet.QuarkOperation memory quarkOperation) + public + returns (QuarkWallet.QuarkOperation memory) + { + return getCancelOperation( + wallet, semiRandomNonce(wallet), abi.encodeWithSignature("cancel(bytes32)", quarkOperation.nonce) + ); + } + + function getCancelOperation(QuarkWallet wallet, bytes32 selfNonce, bytes memory callData) + public + returns (QuarkWallet.QuarkOperation memory) + { + bytes memory cancelScript = new YulHelper().getCode("Cancel.sol/Cancel.json"); + address scriptAddress = wallet.codeJar().saveCode(cancelScript); + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = cancelScript; + return QuarkWallet.QuarkOperation({ + scriptAddress: scriptAddress, + scriptSources: scriptSources, + scriptCalldata: callData, + nonce: selfNonce, + isReplayable: false, + expiry: block.timestamp + 1000 + }); + } + + /// @dev Note: not sufficiently random for non-test case usage. + function semiRandomNonce(QuarkWallet wallet) public view returns (bytes32) { + if (address(wallet).code.length == 0) { + revert SemiRandomNonceRequiresQuarkNonceManagerOrInitializedQuarkWallet(address(wallet)); + } + + return semiRandomNonce(wallet.nonceManager(), wallet); + } + + /// @dev Note: not sufficiently random for non-test case usage. + function semiRandomNonce(QuarkNonceManager quarkNonceManager, QuarkWallet wallet) public view returns (bytes32) { + bytes32 nonce = bytes32(uint256(keccak256(abi.encodePacked(block.timestamp))) - 1); + while (true) { + if (quarkNonceManager.submissions(address(wallet), nonce) == bytes32(uint256(0))) { + return nonce; + } + + nonce = bytes32(uint256(keccak256(abi.encodePacked(nonce))) - 1); + } + revert Impossible(); + } + + function incrementNonce(bytes32 nonce) public pure returns (bytes32) { + return bytes32(uint256(nonce) + 1); + } } diff --git a/test/lib/ReentrantTransfer.sol b/test/lib/ReentrantTransfer.sol index 011abaf2..af5908fa 100644 --- a/test/lib/ReentrantTransfer.sol +++ b/test/lib/ReentrantTransfer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; diff --git a/test/lib/Reverts.sol b/test/lib/Reverts.sol index 0cebf8c6..2b097fd1 100644 --- a/test/lib/Reverts.sol +++ b/test/lib/Reverts.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import {CodeJar} from "codejar/src/CodeJar.sol"; diff --git a/test/lib/SignatureHelper.sol b/test/lib/SignatureHelper.sol index 7f855f64..070e426e 100644 --- a/test/lib/SignatureHelper.sol +++ b/test/lib/SignatureHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "quark-core/src/QuarkWallet.sol"; @@ -55,6 +55,7 @@ contract SignatureHelper is Test { abi.encode( QuarkWalletMetadata.QUARK_OPERATION_TYPEHASH, op.nonce, + op.isReplayable, op.scriptAddress, keccak256(encodedArray), keccak256(op.scriptCalldata), diff --git a/test/lib/VictimERC777.sol b/test/lib/VictimERC777.sol index c2622266..a3cb131c 100644 --- a/test/lib/VictimERC777.sol +++ b/test/lib/VictimERC777.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "openzeppelin/token/ERC777/ERC777.sol"; import "openzeppelin/token/ERC20/ERC20.sol"; diff --git a/test/lib/YulHelper.sol b/test/lib/YulHelper.sol index 6c912f53..39626a34 100644 --- a/test/lib/YulHelper.sol +++ b/test/lib/YulHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; diff --git a/test/on-chain-info-verification/MorphoInfo.t.sol b/test/on-chain-info-verification/MorphoInfo.t.sol index ded392ba..e10d3d93 100644 --- a/test/on-chain-info-verification/MorphoInfo.t.sol +++ b/test/on-chain-info-verification/MorphoInfo.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; +pragma solidity 0.8.27; import "forge-std/Test.sol"; import "forge-std/console.sol";