From 3a4c3f6644997f52e8518e8c273609e8e49b53a4 Mon Sep 17 00:00:00 2001 From: Geoff Hayes Date: Fri, 23 Feb 2024 13:56:39 -0800 Subject: [PATCH] Run CodeJar Code Constructors (#164) * Run CodeJar Code Constructors Okay okay okay, after long last, we do the Solidity thing to feed the Solidity monster. Specifically, here we run CodeJar's constructor code as initCode, instead of returning it via a newly assembled script. This has advantages and drawbacks, specifically: 1. Pro: scripts can be easily verified 2. Pro: scripts can use const immutables 3. Con: scripts are less deterministic 4. Con: scripts can't be raw evm opcodes 5. Con: scripts can be more malicious (e.g. `owner = tx.origin`) Overall it looks like the pros outweigh the cons, esp. when you consider people are only running scripts they truly trust and thus (4) and (5) become pretty nullified. Weirdly aside of a few tests that were pretty degenerative (testing specific nuances of CodeJar itself), every other test case is passing. We'll need to rethink the deeper core CodeJar tests, but the vast majority of things are good with this change. Weird. Patches: * Add a constructor with const immutable check * Check creation code > 0, and add a few more test cases * Lint * Add a variety of test cases for metamorphic contracts and the wackiness they can present * refactor: scriptSource***s***, and rough test fixes--will go back over * chore: forge fmt, but on nightly this time * bugfix+test+hack: try to fix array encoding and add a test case for structhash, but this isn't working very well * chore: forge fmt * test: address FIXMEs and update outdated test cases * Fix Struct Hash Test This fixes struct hash test. As we build the struct hash otherwise in JavaScript, we need to fix a few values, which isn't a problem per se since it's testing the same thing we'd expect. Patches: * Run format * Add true external Eip-712 signature test * Move `stub` to `YulHelper` * refactor/minor: address style comments in QuarkWallet.sol * test: bring back the empty code EIP-1271 signer test case * refactor: remove InvalidScriptAddress and just fall on EmptyCode * minor: fix compiler warning about unused variable * doc: add a TODO for refactoring QuarkOperationHelper * chore: forge fmt * chore: commit new gas snapshot --------- Co-authored-by: fluffywaffles --- .gas-snapshot | 359 +++++++++--------- script/DeployQuarkWalletFactory.s.sol | 5 +- script/DeployTerminalScripts.s.sol | 14 +- src/codejar/src/CodeJar.sol | 50 ++- src/codejar/src/CodeJarStub.sol | 45 --- src/quark-core/src/QuarkWallet.sol | 50 +-- test/ReplayableTransactions.t.sol | 2 +- test/codejar/CodeJar.t.sol | 151 ++++++-- test/legend-scripts/ApproveAndSwap.t.sol | 6 +- test/legend-scripts/CometClaimRewards.t.sol | 2 +- .../CometRepayAndWithdrawMultipleAssets.t.sol | 6 +- test/legend-scripts/CometSupplyActions.t.sol | 2 +- .../CometSupplyMultipleAssetsAndBorrow.t.sol | 6 +- .../legend-scripts/CometWithdrawActions.t.sol | 2 +- test/legend-scripts/GetDrip.t.sol | 2 +- test/legend-scripts/TransferActions.t.sol | 10 +- test/legend-scripts/UniswapSwapActions.t.sol | 2 +- test/lib/ConstructorReverter.sol | 10 + test/lib/EmptyCode.sol | 10 + test/lib/Mememe.sol | 15 + test/lib/Ping.yul0 | 7 + test/lib/QuarkOperationHelper.sol | 20 +- test/lib/Redeployer.sol | 14 + test/lib/Reverts.sol | 6 +- test/lib/SignatureHelper.sol | 12 +- test/lib/TickCounter.sol | 32 ++ test/lib/Wacky.sol | 46 +++ test/lib/YulHelper.sol | 8 +- .../ConditionalMulticall.t.sol | 4 +- test/quark-core-scripts/Ethcall.t.sol | 2 +- test/quark-core-scripts/Multicall.t.sol | 10 +- .../quark-core-scripts/UniswapFlashLoan.t.sol | 6 +- .../UniswapFlashSwapExactOut.t.sol | 6 +- test/quark-core/Callbacks.t.sol | 31 +- test/quark-core/EIP1271.t.sol | 2 +- test/quark-core/EIP712.t.sol | 75 +++- test/quark-core/Executor.t.sol | 8 +- test/quark-core/QuarkStateManager.t.sol | 4 +- test/quark-core/QuarkWallet.t.sol | 270 ++++++++----- test/quark-core/Reverts.t.sol | 8 +- test/quark-core/isValidSignature.t.sol | 5 +- test/quark-core/periphery/BatchExecutor.t.sol | 10 +- test/quark-proxy/QuarkMinimalProxy.t.sol | 2 +- .../quark-proxy/QuarkWalletProxyFactory.t.sol | 85 ++++- 44 files changed, 888 insertions(+), 534 deletions(-) delete mode 100644 src/codejar/src/CodeJarStub.sol create mode 100644 test/lib/ConstructorReverter.sol create mode 100644 test/lib/EmptyCode.sol create mode 100644 test/lib/Mememe.sol create mode 100644 test/lib/Ping.yul0 create mode 100644 test/lib/Redeployer.sol create mode 100644 test/lib/TickCounter.sol create mode 100644 test/lib/Wacky.sol diff --git a/.gas-snapshot b/.gas-snapshot index fe0d9382..dcd40fba 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,187 +1,192 @@ -BatchExecutorTest:testBatchExecute() (gas: 137003) -BatchExecutorTest:testBatchExecuteDoesNotRevertIfAnyCallsRevert() (gas: 130996) -CallbacksTest:testAllowNestedCallbacks() (gas: 154379) -CallbacksTest:testCallbackFromCounter() (gas: 108407) -CallbacksTest:testCallcodeReentrancyExploitWithProtectedScript() (gas: 101443) -CallbacksTest:testCallcodeReentrancyExploitWithUnprotectedScript() (gas: 107368) -CallbacksTest:testCallcodeReentrancyProtectionWithProtectedScript() (gas: 194611) -CallbacksTest:testClearCallback() (gas: 156405) -CallbacksTest:testNestedCallWithNoCallbackSucceeds() (gas: 122348) -CallbacksTest:testPayableCallback() (gas: 115994) -CallbacksTest:testRevertsOnCallbackWhenNoActiveCallback() (gas: 102862) -CodeJarTest:testCodeJarCodeDoesNotExistOnEmptyScriptWithETH() (gas: 57562) -CodeJarTest:testCodeJarCounter() (gas: 361936) -CodeJarTest:testCodeJarDeployConstructor() (gas: 47859) -CodeJarTest:testCodeJarDeployNotAffectedByChangedCodeHash() (gas: 84970) -CodeJarTest:testCodeJarDifferentZeros() (gas: 91668) -CodeJarTest:testCodeJarFirstDeploy() (gas: 45402) -CodeJarTest:testCodeJarInitCodeLength() (gas: 669) -CodeJarTest:testCodeJarInputVariety() (gas: 410742) -CodeJarTest:testCodeJarLarge() (gas: 71995573) -CodeJarTest:testCodeJarSecondDeploy() (gas: 48537) -CodeJarTest:testCodeJarSelfDestruct() (gas: 51794) -CometClaimRewardsTest:testClaimComp() (gas: 135520) -CometRepayAndWithdrawMultipleAssetsTest:testInvalidInput() (gas: 81340) -CometRepayAndWithdrawMultipleAssetsTest:testRepayAndWithdrawMultipleAssets() (gas: 164256) -CometSupplyMultipleAssetsAndBorrowTest:testInvalidInput() (gas: 81486) -CometSupplyMultipleAssetsAndBorrowTest:testSupplyMultipleAssetsAndBorrow() (gas: 306042) -ConditionalMulticallTest:testConditionalRunEmptyInputIsValid() (gas: 52024) -ConditionalMulticallTest:testConditionalRunInvalidInput() (gas: 69680) -ConditionalMulticallTest:testConditionalRunMulticallError() (gas: 317500) -ConditionalMulticallTest:testConditionalRunOnPeriodicRepay() (gas: 344440) -ConditionalMulticallTest:testConditionalRunPassed() (gas: 285868) -ConditionalMulticallTest:testConditionalRunUnmet() (gas: 104626) -EIP1271Test:testReturnsMagicValueForValidSignature() (gas: 82891) -EIP1271Test:testRevertsIfSignerContractDoesNotReturnMagic() (gas: 21500) -EIP712Test:testExecuteQuarkOperation() (gas: 82325) -EIP712Test:testNonceIsNotSetForReplayableOperation() (gas: 156606) -EIP712Test:testRequirements() (gas: 203856) -EIP712Test:testRevertBadRequirements() (gas: 31084) -EIP712Test:testRevertsForBadCalldata() (gas: 24232) -EIP712Test:testRevertsForBadCode() (gas: 22533) -EIP712Test:testRevertsForBadExpiry() (gas: 24778) -EIP712Test:testRevertsForExpiredSignature() (gas: 14315) -EIP712Test:testRevertsInvalidS() (gas: 21356) -EIP712Test:testRevertsOnReusedNonce() (gas: 109198) -EthcallTest:testEthcallCallReraiseError() (gas: 82760) -EthcallTest:testEthcallCounter() (gas: 75989) -EthcallTest:testEthcallShouldReturnCallResult() (gas: 57640) -EthcallTest:testEthcallSupplyUSDCToComet() (gas: 182172) -EthcallTest:testEthcallWithdrawUSDCFromComet() (gas: 311372) -ExecutorTest:testExecutorCanDirectCall() (gas: 131124) -ExecutorTest:testExecutorCanDirectCallBySig() (gas: 123408) -MulticallTest:testCallcodeToMulticallSucceedsWhenUninitialized() (gas: 95697) -MulticallTest:testCanBeGriefedByWritingAddressToQuarkWalletStorage() (gas: 78448) -MulticallTest:testCreateSubWalletAndExecute() (gas: 603891) -MulticallTest:testEmptyInputIsValid() (gas: 61447) -MulticallTest:testExecutorCanMulticallAcrossSubwallets() (gas: 313126) +ApproveAndSwapTest:testSwap() (gas: 286150) +ApproveAndSwapTest:testSwapFailsIfWeExpectedTooMuch() (gas: 365405) +ApproveAndSwapTest:testSwapFailsWithNoApproval() (gas: 122970) +BatchExecutorTest:testBatchExecute() (gas: 128594) +BatchExecutorTest:testBatchExecuteDoesNotRevertIfAnyCallsRevert() (gas: 121550) +CallbacksTest:testAllowNestedCallbacks() (gas: 153970) +CallbacksTest:testCallbackFromCounter() (gas: 98822) +CallbacksTest:testCallcodeReentrancyExploitWithProtectedScript() (gas: 101366) +CallbacksTest:testCallcodeReentrancyExploitWithUnprotectedScript() (gas: 107291) +CallbacksTest:testCallcodeReentrancyProtectionWithProtectedScript() (gas: 194379) +CallbacksTest:testClearCallback() (gas: 138973) +CallbacksTest:testNestedCallWithNoCallbackSucceeds() (gas: 121927) +CallbacksTest:testPayableCallback() (gas: 106412) +CallbacksTest:testRevertsOnCallbackWhenNoActiveCallback() (gas: 97213) +CodeJarTest:testCodeJarCanBeWacky() (gas: 139555) +CodeJarTest:testCodeJarCanDeployCodeThatHadEthSent() (gas: 4945643) +CodeJarTest:testCodeJarCounter() (gas: 358680) +CodeJarTest:testCodeJarDeployNotAffectedByChangedCodeHash() (gas: 1052869) +CodeJarTest:testCodeJarDeploysAnother() (gas: 236368) +CodeJarTest:testCodeJarDifferentZeros() (gas: 2033220) +CodeJarTest:testCodeJarFirstDeploy() (gas: 1016127) +CodeJarTest:testCodeJarInputVariety() (gas: 20799089) +CodeJarTest:testCodeJarLarge() (gas: 76749588) +CodeJarTest:testCodeJarOnSelfDestructingConstructor() (gas: 54682) +CodeJarTest:testCodeJarRefusesToDeployEmptyCode() (gas: 2963612) +CodeJarTest:testCodeJarSecondDeploy() (gas: 1018505) +CodeJarTest:testCodeJarSelfDestruct() (gas: 1022512) +CodeJarTest:testCodeJarStoresSelfReference() (gas: 107362) +CodeJarTest:testCodeJarTickCounter() (gas: 176881) +CodeJarTest:testRevertsOnConstructorRevert() (gas: 85881) +CometClaimRewardsTest:testClaimComp() (gas: 130863) +CometRepayAndWithdrawMultipleAssetsTest:testInvalidInput() (gas: 68708) +CometRepayAndWithdrawMultipleAssetsTest:testRepayAndWithdrawMultipleAssets() (gas: 154556) +CometSupplyMultipleAssetsAndBorrowTest:testInvalidInput() (gas: 68659) +CometSupplyMultipleAssetsAndBorrowTest:testSupplyMultipleAssetsAndBorrow() (gas: 296189) +ConditionalMulticallTest:testConditionalRunEmptyInputIsValid() (gas: 51933) +ConditionalMulticallTest:testConditionalRunInvalidInput() (gas: 69520) +ConditionalMulticallTest:testConditionalRunMulticallError() (gas: 317339) +ConditionalMulticallTest:testConditionalRunOnPeriodicRepay() (gas: 343915) +ConditionalMulticallTest:testConditionalRunPassed() (gas: 285755) +ConditionalMulticallTest:testConditionalRunUnmet() (gas: 104466) +EIP1271Test:testReturnsMagicValueForValidSignature() (gas: 74827) +EIP1271Test:testRevertsIfSignerContractDoesNotReturnMagic() (gas: 18567) +EIP712Test:testExecuteQuarkOperation() (gas: 74238) +EIP712Test:testNonceIsNotSetForReplayableOperation() (gas: 143649) +EIP712Test:testRequirements() (gas: 174895) +EIP712Test:testRevertBadRequirements() (gas: 27207) +EIP712Test:testRevertsForBadCalldata() (gas: 21254) +EIP712Test:testRevertsForBadCode() (gas: 23320) +EIP712Test:testRevertsForBadExpiry() (gas: 21800) +EIP712Test:testRevertsForExpiredSignature() (gas: 12273) +EIP712Test:testRevertsInvalidS() (gas: 18378) +EIP712Test:testRevertsOnReusedNonce() (gas: 92638) +EIP712Test:testStructHash() (gas: 9223372036854754743) +EthcallTest:testEthcallCallReraiseError() (gas: 76984) +EthcallTest:testEthcallCounter() (gas: 70385) +EthcallTest:testEthcallShouldReturnCallResult() (gas: 53164) +EthcallTest:testEthcallSupplyUSDCToComet() (gas: 173216) +EthcallTest:testEthcallWithdrawUSDCFromComet() (gas: 297934) +ExecutorTest:testExecutorCanDirectCall() (gas: 131336) +ExecutorTest:testExecutorCanDirectCallBySig() (gas: 118123) +GetDripTest:testDrip() (gas: 121191) +MulticallTest:testCallcodeToMulticallSucceedsWhenUninitialized() (gas: 86183) +MulticallTest:testCanBeGriefedByWritingAddressToQuarkWalletStorage() (gas: 68504) +MulticallTest:testCreateSubWalletAndExecute() (gas: 604015) +MulticallTest:testEmptyInputIsValid() (gas: 53195) +MulticallTest:testExecutorCanMulticallAcrossSubwallets() (gas: 305792) MulticallTest:testInitializesStorageProperly() (gas: 27124) -MulticallTest:testInvalidInput() (gas: 81071) -MulticallTest:testInvokeCounterTwice() (gas: 95920) -MulticallTest:testMulticallError() (gas: 322674) -MulticallTest:testMulticallShouldReturnCallResults() (gas: 99025) +MulticallTest:testInvalidInput() (gas: 71146) +MulticallTest:testInvokeCounterTwice() (gas: 86406) +MulticallTest:testMulticallError() (gas: 312730) +MulticallTest:testMulticallShouldReturnCallResults() (gas: 89560) MulticallTest:testNoOpWhenInitializedMultipleTimes() (gas: 28495) -MulticallTest:testRevertsForInvalidCallContext() (gas: 36715) -MulticallTest:testSupplyWETHWithdrawUSDCOnComet() (gas: 272319) +MulticallTest:testRevertsForInvalidCallContext() (gas: 36831) +MulticallTest:testSupplyWETHWithdrawUSDCOnComet() (gas: 262800) NonceTest:testIsSet() (gas: 30425) NonceTest:testNextUnusedNonce() (gas: 40288) NonceTest:testNonLinearNonce() (gas: 30757) -QuarkFactoryTest:testInvariantAddressesBetweenNonces() (gas: 2877385) -QuarkFactoryTest:testQuarkFactoryDeployToDeterministicAddresses() (gas: 2877148) -QuarkFactoryTest:testQuarkFactoryDeployTwice() (gas: 8937393460516818819) -QuarkMinimalProxyTest:testSignerExecutor() (gas: 75418) -QuarkStateManagerTest:testNonceZeroIsValid() (gas: 91456) -QuarkStateManagerTest:testReadStorageForWallet() (gas: 148551) +QuarkFactoryTest:testInvariantAddressesBetweenNonces() (gas: 2965560) +QuarkFactoryTest:testQuarkFactoryDeployToDeterministicAddresses() (gas: 2965323) +QuarkFactoryTest:testQuarkFactoryDeployTwice() (gas: 8937393460516821550) +QuarkMinimalProxyTest:testSignerExecutor() (gas: 75271) +QuarkStateManagerTest:testNonceZeroIsValid() (gas: 91528) +QuarkStateManagerTest:testReadStorageForWallet() (gas: 148666) QuarkStateManagerTest:testRevertsForNoActiveNonce() (gas: 22727) -QuarkStateManagerTest:testRevertsIfScriptAddressIsEOA() (gas: 61796) -QuarkStateManagerTest:testRevertsIfScriptAddressIsNull() (gas: 42218) +QuarkStateManagerTest:testRevertsIfScriptAddressIsEOA() (gas: 61832) +QuarkStateManagerTest:testRevertsIfScriptAddressIsNull() (gas: 42254) QuarkStateManagerTest:testSetActiveNonceAndCallbackNotImplemented() (gas: 92094) -QuarkWalletProxyFactoryTest:testCreateAdditionalWalletWithSalt() (gas: 212358) -QuarkWalletProxyFactoryTest:testCreateAndExecuteCreatesWallet() (gas: 415202) -QuarkWalletProxyFactoryTest:testCreateAndExecuteSetsMsgSender() (gas: 225210) -QuarkWalletProxyFactoryTest:testCreateAndExecuteWithSalt() (gas: 419672) -QuarkWalletProxyFactoryTest:testCreateAndExecuteWithSaltSetsMsgSender() (gas: 225086) -QuarkWalletProxyFactoryTest:testCreateRevertsOnRepeat() (gas: 8937393460516733228) -QuarkWalletProxyFactoryTest:testCreatesWalletAtDeterministicAddress() (gas: 222087) -QuarkWalletProxyFactoryTest:testExecuteOnExistingWallet() (gas: 413839) -QuarkWalletProxyFactoryTest:testExecutorIsOtherWallet() (gas: 283334) -QuarkWalletProxyFactoryTest:testExecutorSetInCreate() (gas: 103547) -QuarkWalletProxyFactoryTest:testVersion() (gas: 6022) -QuarkWalletTest:testAtomicIncrementerWithScriptAddress() (gas: 71516) -QuarkWalletTest:testAtomicIncrementerWithScriptSource() (gas: 79388) -QuarkWalletTest:testAtomicMaxCounterScriptWithScriptAddress() (gas: 270483) -QuarkWalletTest:testAtomicMaxCounterScriptWithScriptSource() (gas: 300896) -QuarkWalletTest:testAtomicPingWithScriptAddress() (gas: 51902) -QuarkWalletTest:testAtomicPingWithScriptSource() (gas: 55055) -QuarkWalletTest:testCanReplaySameScriptWithDifferentCall() (gas: 163827) -QuarkWalletTest:testDirectExecuteFromEOA() (gas: 68095) -QuarkWalletTest:testDirectExecuteFromOtherQuarkWallet() (gas: 121714) -QuarkWalletTest:testDisallowAllNullNoopScript() (gas: 53935) -QuarkWalletTest:testEmitsEventsInDirectExecute() (gas: 46875) -QuarkWalletTest:testEmitsEventsInExecuteQuarkOperation() (gas: 91486) -QuarkWalletTest:testEmptyScriptRevertForScriptAddress() (gas: 67189) -QuarkWalletTest:testEmptyScriptRevertForScriptSource() (gas: 8893) -QuarkWalletTest:testGetCodeJar() (gas: 11248) +QuarkWalletProxyFactoryTest:testCreateAdditionalWalletWithSalt() (gas: 212394) +QuarkWalletProxyFactoryTest:testCreateAndExecuteCreatesWallet() (gas: 411294) +QuarkWalletProxyFactoryTest:testCreateAndExecuteSetsMsgSender() (gas: 223498) +QuarkWalletProxyFactoryTest:testCreateAndExecuteWithSalt() (gas: 415826) +QuarkWalletProxyFactoryTest:testCreateAndExecuteWithSaltSetsMsgSender() (gas: 395298) +QuarkWalletProxyFactoryTest:testCreateRevertsOnRepeat() (gas: 8937393460516733229) +QuarkWalletProxyFactoryTest:testCreatesWalletAtDeterministicAddress() (gas: 222151) +QuarkWalletProxyFactoryTest:testExecuteOnExistingWallet() (gas: 409966) +QuarkWalletProxyFactoryTest:testExecutorIsOtherWallet() (gas: 281937) +QuarkWalletProxyFactoryTest:testExecutorSetInCreate() (gas: 103562) +QuarkWalletProxyFactoryTest:testVersion() (gas: 6014) +QuarkWalletTest:testAtomicIncrementer() (gas: 71727) +QuarkWalletTest:testAtomicMaxCounterScript() (gas: 270364) +QuarkWalletTest:testAtomicPing() (gas: 51591) +QuarkWalletTest:testAtomicPingWithExternalSignature() (gas: 98806) +QuarkWalletTest:testCanReplaySameScriptWithDifferentCall() (gas: 156838) +QuarkWalletTest:testDirectExecuteFromEOA() (gas: 68102) +QuarkWalletTest:testDirectExecuteFromOtherQuarkWallet() (gas: 117247) +QuarkWalletTest:testDisallowAllNullScriptAddress() (gas: 221752) +QuarkWalletTest:testEmitsEventsInDirectExecute() (gas: 46916) +QuarkWalletTest:testEmitsEventsInExecuteQuarkOperation() (gas: 88444) +QuarkWalletTest:testGetCodeJar() (gas: 11094) QuarkWalletTest:testGetExecutor() (gas: 5469) -QuarkWalletTest:testGetSigner() (gas: 8566) -QuarkWalletTest:testGetStateManager() (gas: 10522) -QuarkWalletTest:testPrecompileBigModExp() (gas: 51056) -QuarkWalletTest:testPrecompileBlake2F() (gas: 53475) -QuarkWalletTest:testPrecompileBn256Add() (gas: 52541) -QuarkWalletTest:testPrecompileBn256ScalarMul() (gas: 56453) -QuarkWalletTest:testPrecompileDataCopy() (gas: 59379) -QuarkWalletTest:testPrecompileEcRecover() (gas: 56530) -QuarkWalletTest:testPrecompileRipemd160() (gas: 52472) -QuarkWalletTest:testPrecompileSha256() (gas: 52955) -QuarkWalletTest:testQuarkOperationWithScriptAddressRevertsIfCallReverts() (gas: 67847) -QuarkWalletTest:testQuarkOperationWithScriptSourceRevertsIfCallReverts() (gas: 75969) -QuarkWalletTest:testRevertOnAllPrecompilesDirectCall() (gas: 633178) -QuarkWalletTest:testRevertsForDirectExecuteByNonExecutorSigner() (gas: 13432) -QuarkWalletTest:testRevertsForOperationWithAddressAndSource() (gas: 14232) -QuarkWalletTest:testRevertsForReplayOfCanceledScript() (gas: 209712) -QuarkWalletTest:testRevertsForReusedNonceWithChangedScript() (gas: 122928) -QuarkWalletTest:testRevertsForUnauthorizedDirectExecuteByRandomAddress() (gas: 15554) -QuarkWalletTest:testSetsMsgSender() (gas: 54204) -QuarkWalletTest:testSetsMsgSenderDuringDirectExecute() (gas: 44838) -ReplayableTransactionsTest:testCancelRecurringPurchase() (gas: 268599) -ReplayableTransactionsTest:testRecurringPurchaseHappyPath() (gas: 210183) -ReplayableTransactionsTest:testRecurringPurchaseMultiplePurchases() (gas: 364820) -ReplayableTransactionsTest:testRecurringPurchaseWithDifferentCalldata() (gas: 590269) -ReplayableTransactionsTest:testRevertsForExpiredQuarkOperation() (gas: 9137) -ReplayableTransactionsTest:testRevertsForExpiredUniswapParams() (gas: 118211) -ReplayableTransactionsTest:testRevertsForPurchaseBeforeNextPurchasePeriod() (gas: 283355) -ReplayableTransactionsTest:testRevertsForPurchasingOverTheLimit() (gas: 283930) -RevertsTest:testRevertsInteger() (gas: 66964) -RevertsTest:testRevertsInvalidOpcode() (gas: 8524964991080582970) -RevertsTest:testRevertsOutOfGas() (gas: 295992) -RevertsTest:testRevertsWhenDividingByZero() (gas: 66810) -SupplyActionsTest:testInvalidInput() (gas: 84345) -SupplyActionsTest:testRepayBorrow() (gas: 102791) -SupplyActionsTest:testSupply() (gas: 144973) -SupplyActionsTest:testSupplyFrom() (gas: 145347) -SupplyActionsTest:testSupplyMultipleCollateral() (gas: 283880) -SupplyActionsTest:testSupplyTo() (gas: 144667) -TransferActionsTest:testRevertsForTransferERC777ReentrancyAttackWithReentrancyGuard() (gas: 162025) -TransferActionsTest:testRevertsForTransferReentrancyAttackWithReentrancyGuard() (gas: 143303) -TransferActionsTest:testRevertsForTransferReentrancyAttackWithoutCallbackEnabled() (gas: 109189) -TransferActionsTest:testRevertsForTransferReentrantAttackWithStolenSignature() (gas: 133107) -TransferActionsTest:testTransferERC20TokenToEOA() (gas: 77948) -TransferActionsTest:testTransferERC20TokenToQuarkWallet() (gas: 79324) -TransferActionsTest:testTransferERC777SuccessWithEvilReceiverWithoutAttackAttempt() (gas: 119320) -TransferActionsTest:testTransferERC777TokenReentrancyAttackSuccessWithCallbackEnabled() (gas: 157305) -TransferActionsTest:testTransferNativeTokenToEOA() (gas: 88949) -TransferActionsTest:testTransferNativeTokenToQuarkWallet() (gas: 63615) -TransferActionsTest:testTransferReentrancyAttackSuccessWithCallbackEnabled() (gas: 141016) -TransferActionsTest:testTransferSuccessWithEvilReceiverWithoutAttackAttempt() (gas: 99719) -UniswapFlashLoanTest:testFlashLoanForCollateralSwapOnCompound() (gas: 430608) -UniswapFlashLoanTest:testRevertsForInsufficientFundsToRepayFlashLoan() (gas: 194136) -UniswapFlashLoanTest:testRevertsForInvalidCaller() (gas: 71218) +QuarkWalletTest:testGetSigner() (gas: 8390) +QuarkWalletTest:testGetStateManager() (gas: 10568) +QuarkWalletTest:testPrecompileBigModExp() (gas: 50864) +QuarkWalletTest:testPrecompileBlake2F() (gas: 53388) +QuarkWalletTest:testPrecompileBn256Add() (gas: 52314) +QuarkWalletTest:testPrecompileBn256ScalarMul() (gas: 56349) +QuarkWalletTest:testPrecompileDataCopy() (gas: 59288) +QuarkWalletTest:testPrecompileEcRecover() (gas: 56404) +QuarkWalletTest:testPrecompileRipemd160() (gas: 52386) +QuarkWalletTest:testPrecompileSha256() (gas: 52776) +QuarkWalletTest:testQuarkOperationRevertsIfCallReverts() (gas: 67370) +QuarkWalletTest:testRevertOnAllPrecompilesDirectCall() (gas: 631274) +QuarkWalletTest:testRevertsForDirectExecuteByNonExecutorSigner() (gas: 13350) +QuarkWalletTest:testRevertsForRandomEmptyScriptAddress() (gas: 116740) +QuarkWalletTest:testRevertsForReplayOfCanceledScript() (gas: 209190) +QuarkWalletTest:testRevertsForReusedNonceWithChangedScript() (gas: 122596) +QuarkWalletTest:testRevertsForUnauthorizedDirectExecuteByRandomAddress() (gas: 15581) +QuarkWalletTest:testSetsMsgSender() (gas: 51142) +QuarkWalletTest:testSetsMsgSenderDuringDirectExecute() (gas: 44967) +ReplayableTransactionsTest:testCancelRecurringPurchase() (gas: 268104) +ReplayableTransactionsTest:testRecurringPurchaseHappyPath() (gas: 210058) +ReplayableTransactionsTest:testRecurringPurchaseMultiplePurchases() (gas: 364397) +ReplayableTransactionsTest:testRecurringPurchaseWithDifferentCalldata() (gas: 589377) +ReplayableTransactionsTest:testRevertsForExpiredQuarkOperation() (gas: 9158) +ReplayableTransactionsTest:testRevertsForExpiredUniswapParams() (gas: 118038) +ReplayableTransactionsTest:testRevertsForPurchaseBeforeNextPurchasePeriod() (gas: 283057) +ReplayableTransactionsTest:testRevertsForPurchasingOverTheLimit() (gas: 283632) +RevertsTest:testRevertsInteger() (gas: 66799) +RevertsTest:testRevertsInvalidOpcode() (gas: 8391762413094949937) +RevertsTest:testRevertsOutOfGas() (gas: 295971) +RevertsTest:testRevertsWhenDividingByZero() (gas: 66645) +SupplyActionsTest:testInvalidInput() (gas: 68288) +SupplyActionsTest:testRepayBorrow() (gas: 90440) +SupplyActionsTest:testSupply() (gas: 132622) +SupplyActionsTest:testSupplyFrom() (gas: 112626) +SupplyActionsTest:testSupplyMultipleCollateral() (gas: 271544) +SupplyActionsTest:testSupplyTo() (gas: 132317) +TransferActionsTest:testRevertsForTransferERC777ReentrancyAttackWithReentrancyGuard() (gas: 152135) +TransferActionsTest:testRevertsForTransferReentrancyAttackWithReentrancyGuard() (gas: 133415) +TransferActionsTest:testRevertsForTransferReentrancyAttackWithoutCallbackEnabled() (gas: 100052) +TransferActionsTest:testRevertsForTransferReentrantAttackWithStolenSignature() (gas: 111367) +TransferActionsTest:testTransferERC20TokenToEOA() (gas: 70878) +TransferActionsTest:testTransferERC20TokenToQuarkWallet() (gas: 72255) +TransferActionsTest:testTransferERC777SuccessWithEvilReceiverWithoutAttackAttempt() (gas: 109769) +TransferActionsTest:testTransferERC777TokenReentrancyAttackSuccessWithCallbackEnabled() (gas: 147784) +TransferActionsTest:testTransferNativeTokenToEOA() (gas: 80108) +TransferActionsTest:testTransferNativeTokenToQuarkWallet() (gas: 55527) +TransferActionsTest:testTransferReentrancyAttackSuccessWithCallbackEnabled() (gas: 131495) +TransferActionsTest:testTransferSuccessWithEvilReceiverWithoutAttackAttempt() (gas: 90172) +UniswapFlashLoanTest:testFlashLoanForCollateralSwapOnCompound() (gas: 430507) +UniswapFlashLoanTest:testRevertsForInsufficientFundsToRepayFlashLoan() (gas: 194012) +UniswapFlashLoanTest:testRevertsForInvalidCaller() (gas: 71058) UniswapFlashLoanTest:testRevertsIfCalledDirectly() (gas: 10769) -UniswapFlashLoanTest:testTokensOrderInvariant() (gas: 96158) -UniswapFlashSwapExactOutTest:testInvalidCallerFlashSwap() (gas: 71181) -UniswapFlashSwapExactOutTest:testNotEnoughToPayFlashSwap() (gas: 296927) +UniswapFlashLoanTest:testTokensOrderInvariant() (gas: 96058) +UniswapFlashSwapExactOutTest:testInvalidCallerFlashSwap() (gas: 71021) +UniswapFlashSwapExactOutTest:testNotEnoughToPayFlashSwap() (gas: 296802) UniswapFlashSwapExactOutTest:testRevertsIfCalledDirectly() (gas: 10871) -UniswapFlashSwapExactOutTest:testUniswapFlashSwapExactOutLeverageComet() (gas: 357074) -UniswapSwapActionsTest:testApprovalRefund() (gas: 179873) -UniswapSwapActionsTest:testBuyAssetOneStop() (gas: 277071) -UniswapSwapActionsTest:testBuyAssetTwoStops() (gas: 383624) -UniswapSwapActionsTest:testSellAssetOneStop() (gas: 274406) -UniswapSwapActionsTest:testSellAssetTwoStops() (gas: 387548) -WithdrawActionsTest:testBorrow() (gas: 164277) -WithdrawActionsTest:testInvalidInput() (gas: 78131) -WithdrawActionsTest:testWithdraw() (gas: 92088) -WithdrawActionsTest:testWithdrawFrom() (gas: 91609) -WithdrawActionsTest:testWithdrawMultipleAssets() (gas: 167792) -WithdrawActionsTest:testWithdrawTo() (gas: 92079) -isValidSignatureTest:testIsValidSignatureForEOAOwner() (gas: 13281) -isValidSignatureTest:testReturnsMagicValueForValidSignature() (gas: 9164) -isValidSignatureTest:testRevertsForEmptyContract() (gas: 11162) -isValidSignatureTest:testRevertsForInvalidSignature() (gas: 16466) -isValidSignatureTest:testRevertsForMessageWithoutDomainTypehash() (gas: 13804) -isValidSignatureTest:testRevertsForPermit2SignatureReuse() (gas: 65300) -isValidSignatureTest:testRevertsForPermit2SignatureWithoutDomainTypehash() (gas: 30890) -isValidSignatureTest:testRevertsForWrongSigner() (gas: 13847) -isValidSignatureTest:testRevertsIfSignatureExceeds65Bytes() (gas: 5698) -isValidSignatureTest:testRevertsIfSignerContractDoesNotReturnMagic() (gas: 9336) -isValidSignatureTest:testRevertsIfSignerContractReverts() (gas: 9153) -isValidSignatureTest:testRevertsInvalidS() (gas: 13014) \ No newline at end of file +UniswapFlashSwapExactOutTest:testUniswapFlashSwapExactOutLeverageComet() (gas: 356974) +UniswapSwapActionsTest:testApprovalRefund() (gas: 164648) +UniswapSwapActionsTest:testBuyAssetOneStop() (gas: 252711) +UniswapSwapActionsTest:testBuyAssetTwoStops() (gas: 359268) +UniswapSwapActionsTest:testSellAssetOneStop() (gas: 250046) +UniswapSwapActionsTest:testSellAssetTwoStops() (gas: 363193) +WithdrawActionsTest:testBorrow() (gas: 154645) +WithdrawActionsTest:testInvalidInput() (gas: 68160) +WithdrawActionsTest:testWithdraw() (gas: 84382) +WithdrawActionsTest:testWithdrawFrom() (gas: 83908) +WithdrawActionsTest:testWithdrawMultipleAssets() (gas: 160105) +WithdrawActionsTest:testWithdrawTo() (gas: 84376) +isValidSignatureTest:testIsValidSignatureForEOAOwner() (gas: 13401) +isValidSignatureTest:testReturnsMagicValueForValidSignature() (gas: 9332) +isValidSignatureTest:testRevertsForEmptyContract() (gas: 11261) +isValidSignatureTest:testRevertsForInvalidSignature() (gas: 16586) +isValidSignatureTest:testRevertsForMessageWithoutDomainTypehash() (gas: 13924) +isValidSignatureTest:testRevertsForPermit2SignatureReuse() (gas: 65540) +isValidSignatureTest:testRevertsForPermit2SignatureWithoutDomainTypehash() (gas: 31010) +isValidSignatureTest:testRevertsForWrongSigner() (gas: 13967) +isValidSignatureTest:testRevertsIfSignatureExceeds65Bytes() (gas: 5734) +isValidSignatureTest:testRevertsIfSignerContractDoesNotReturnMagic() (gas: 9504) +isValidSignatureTest:testRevertsIfSignerContractReverts() (gas: 9297) +isValidSignatureTest:testRevertsInvalidS() (gas: 13134) \ No newline at end of file diff --git a/script/DeployQuarkWalletFactory.s.sol b/script/DeployQuarkWalletFactory.s.sol index a901c421..acb1464e 100644 --- a/script/DeployQuarkWalletFactory.s.sol +++ b/script/DeployQuarkWalletFactory.s.sol @@ -55,11 +55,10 @@ contract DeployQuarkWalletFactory is Script { CodeJar codeJar = QuarkWallet(payable(quarkFactory.quarkWalletProxyFactory().walletImplementation())).codeJar(); - ethcall = Ethcall(codeJar.saveCode(vm.getDeployedCode(string.concat("out/", "Ethcall.sol/Ethcall.json")))); + ethcall = Ethcall(codeJar.saveCode(vm.getCode(string.concat("out/", "Ethcall.sol/Ethcall.json")))); console.log("Ethcall Deployed:", address(ethcall)); - multicall = - Multicall(codeJar.saveCode(vm.getDeployedCode(string.concat("out/", "Multicall.sol/Multicall.json")))); + multicall = Multicall(codeJar.saveCode(vm.getCode(string.concat("out/", "Multicall.sol/Multicall.json")))); console.log("Multicall Deployed:", address(multicall)); console.log("============================================================="); diff --git a/script/DeployTerminalScripts.s.sol b/script/DeployTerminalScripts.s.sol index 1dac23ef..7ee2463f 100644 --- a/script/DeployTerminalScripts.s.sol +++ b/script/DeployTerminalScripts.s.sol @@ -52,40 +52,40 @@ contract DeployTerminalScripts is Script { console.log("Deploying Terminal Scripts"); cometSupplyActions = CometSupplyActions( - codeJar.saveCode(vm.getDeployedCode(string.concat("out/", "LegendScript.sol/CometSupplyActions.json"))) + codeJar.saveCode(vm.getCode(string.concat("out/", "LegendScript.sol/CometSupplyActions.json"))) ); console.log("CometSupplyActions Deployed:", address(cometSupplyActions)); cometWithdrawActions = CometWithdrawActions( - codeJar.saveCode(vm.getDeployedCode(string.concat("out/", "LegendScript.sol/CometWithdrawActions.json"))) + codeJar.saveCode(vm.getCode(string.concat("out/", "LegendScript.sol/CometWithdrawActions.json"))) ); console.log("CometWithdrawActions Deployed:", address(cometWithdrawActions)); uniswapSwapActions = UniswapSwapActions( - codeJar.saveCode(vm.getDeployedCode(string.concat("out/", "LegendScript.sol/UniswapSwapActions.json"))) + codeJar.saveCode(vm.getCode(string.concat("out/", "LegendScript.sol/UniswapSwapActions.json"))) ); console.log("UniswapSwapActions Deployed:", address(uniswapSwapActions)); transferActions = TransferActions( - codeJar.saveCode(vm.getDeployedCode(string.concat("out/", "LegendScript.sol/TransferActions.json"))) + codeJar.saveCode(vm.getCode(string.concat("out/", "LegendScript.sol/TransferActions.json"))) ); console.log("TransferActions Deployed:", address(transferActions)); cometClaimRewards = CometClaimRewards( - codeJar.saveCode(vm.getDeployedCode(string.concat("out/", "LegendScript.sol/CometClaimRewards.json"))) + codeJar.saveCode(vm.getCode(string.concat("out/", "LegendScript.sol/CometClaimRewards.json"))) ); console.log("CometClaimRewards Deployed:", address(cometClaimRewards)); cometSupplyMultipleAssetsAndBorrow = CometSupplyMultipleAssetsAndBorrow( codeJar.saveCode( - vm.getDeployedCode(string.concat("out/", "LegendScript.sol/CometSupplyMultipleAssetsAndBorrow.json")) + vm.getCode(string.concat("out/", "LegendScript.sol/CometSupplyMultipleAssetsAndBorrow.json")) ) ); console.log("CometSupplyMultipleAssetsAndBorrow Deployed:", address(cometSupplyMultipleAssetsAndBorrow)); cometRepayAndWithdrawMultipleAssets = CometRepayAndWithdrawMultipleAssets( codeJar.saveCode( - vm.getDeployedCode(string.concat("out/", "LegendScript.sol/CometRepayAndWithdrawMultipleAssets.json")) + vm.getCode(string.concat("out/", "LegendScript.sol/CometRepayAndWithdrawMultipleAssets.json")) ) ); console.log("CometRepayAndWithdrawMultipleAssets Deployed:", address(cometRepayAndWithdrawMultipleAssets)); diff --git a/src/codejar/src/CodeJar.sol b/src/codejar/src/CodeJar.sol index 613f265f..56a21a4f 100644 --- a/src/codejar/src/CodeJar.sol +++ b/src/codejar/src/CodeJar.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity 0.8.23; -import {CodeJarStub} from "./CodeJarStub.sol"; - /** * @title Code Jar * @notice Deploys contract code to deterministic addresses @@ -12,26 +10,33 @@ contract CodeJar { /** * @notice Deploys the code via Code Jar, no-op if it already exists * @dev This call is meant to be idemponent and fairly inexpensive on a second call - * @param code The runtime bytecode of the code to save - * @return The address of the contract that matches the input code + * @param code The creation bytecode of the code to save + * @return The address of the contract that matches the input code's contructor output */ - function saveCode(bytes calldata code) external returns (address) { + function saveCode(bytes memory code) external returns (address) { address codeAddress = getCodeAddress(code); - bytes32 codeAddressHash = codeAddress.codehash; - if (codeAddressHash == keccak256(code)) { - // Code is already deployed and matches expected code + if (codeAddress.code.length > 0) { + // Code is already deployed return codeAddress; } else { // The code has not been deployed here (or it was deployed and destructed). - CodeJarStub script; - bytes memory initCode = abi.encodePacked(type(CodeJarStub).creationCode, code); + address script; assembly { - script := create2(0, add(initCode, 0x20), mload(initCode), 0) + script := create2(0, add(code, 0x20), mload(code), 0) } // Posit: these cannot fail and are purely defense-in-depth - require(address(script) == codeAddress); + require(script == codeAddress); + + uint256 scriptSz; + assembly { + scriptSz := extcodesize(script) + } + + // Disallow the empty code + // Note: script can still selfdestruct + require(scriptSz > 0); return codeAddress; } @@ -45,27 +50,16 @@ contract CodeJar { function codeExists(bytes calldata code) external view returns (bool) { address codeAddress = getCodeAddress(code); - return codeAddress.code.length != 0 && codeAddress.codehash == keccak256(code); + return codeAddress.code.length > 0; } /** - * @dev Returns the create2 address based on CodeJarStub - * @return The create2 address to deploy this code (via CodeJarStub) + * @dev Returns the create2 address based on the creation code + * @return The create2 address to deploy this code (via init code) */ - function getCodeAddress(bytes memory code) internal view returns (address) { + function getCodeAddress(bytes memory code) public view returns (address) { return address( - uint160( - uint256( - keccak256( - abi.encodePacked( - bytes1(0xff), - address(this), - uint256(0), - keccak256(abi.encodePacked(type(CodeJarStub).creationCode, code)) - ) - ) - ) - ) + uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), uint256(0), keccak256(code))))) ); } } diff --git a/src/codejar/src/CodeJarStub.sol b/src/codejar/src/CodeJarStub.sol deleted file mode 100644 index 66e7c4e8..00000000 --- a/src/codejar/src/CodeJarStub.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -pragma solidity 0.8.23; - -/** - * @title Code Jar Stub - * @notice A contract which short-circuits in its constructor to return its own input as deployed code. - * @author Compound Labs, Inc. - */ -contract CodeJarStub { - /** - * @notice Constructor which returns its input instead of the code of this shell contract - * @dev This allows us to deterministically deploy arbitrary code - */ - constructor() payable { - assembly { - // Note: this short-circuits the constructor, which usually returns this contract's - // own "deployedCode" as its return value. Thus, the input `code` _becomes_ - // this stub's deployedCode on chain, allowing you to deploy a contract - // with any runtime code. - // - // Note: `return`ing from a constructor is not documented in Solidity. This could be - // considered to breach on "undocumented" behavior. This functionality does - // **not** play well with const immutables. - // Note: Magic numbers are weird. Here, we pick a number from observing the deployed size - // of this contract in the output of the build. A few points: a) we would prefer that - // we could do `type(CodeJarStub).creationCode.length` in this code, but that's - // expressly forbidden by Solidity. That would be fine, except in Solidity's own - // Yul code, they use `datasize("CodeJarStub")` and _that's_ okay for some reason, - // b) the idea of knowing where the constructor args start based on knowing the code size, - // via `datasize("CodeJarStub")` is perfectly normal and the only way to decode arguments, - // so the weird part here is simply the idea of hard-coding it since Solidity doesn't - // expose the size of the creation code itself to contracts, c) we tried to use - // `const programSz = type(CodeJarStubSize).creationCode.length` as a contract-constant, - // however, Solidity doesn't believe that to be a constant and thus creates runtime code - // for that. Weirdly `keccak256(type(CodeJarStubSize).creationCode)` is considered to be - // a constant, but I disgress, d) we test this value in a variety of ways. If the magic - // value truly changes, then the test cases would fail. We both check for it expressly, - // but also any test that relies on this working would immediately break otherwise. - let programSz := 20 // It's magic. It's pure darned magic. Please don't look behind the curtain. - let argSz := sub(codesize(), programSz) - codecopy(0, programSz, argSz) - return(0, argSz) - } - } -} diff --git a/src/quark-core/src/QuarkWallet.sol b/src/quark-core/src/QuarkWallet.sol index af0981cf..9e15ff77 100644 --- a/src/quark-core/src/QuarkWallet.sol +++ b/src/quark-core/src/QuarkWallet.sol @@ -22,7 +22,7 @@ library QuarkWalletMetadata { /// @notice The EIP-712 typehash for authorizing an operation for this version of QuarkWallet bytes32 internal constant QUARK_OPERATION_TYPEHASH = keccak256( - "QuarkOperation(uint96 nonce,address scriptAddress,bytes scriptSource,bytes scriptCalldata,uint256 expiry)" + "QuarkOperation(uint96 nonce,address scriptAddress,bytes[] scriptSources,bytes scriptCalldata,uint256 expiry)" ); /// @notice The EIP-712 typehash for authorizing an EIP-1271 signature for this version of QuarkWallet @@ -50,7 +50,6 @@ interface HasSignerExecutor { * @author Compound Labs, Inc. */ contract QuarkWallet is IERC1271 { - error AmbiguousScript(); error BadSignatory(); error EmptyCode(); error InvalidEIP1271Signature(); @@ -101,16 +100,10 @@ contract QuarkWallet is IERC1271 { struct QuarkOperation { /// @notice Nonce identifier for the operation uint96 nonce; - /** - * @notice The address of the transaction script to run - * @dev Should be set as address(0) when `scriptSource` is non-empty - */ + /// @notice The address of the transaction script to run address scriptAddress; - /** - * @notice The runtime bytecode of the transaction script to run - * @dev Should be set to empty bytes when `scriptAddress` is non-zero - */ - bytes scriptSource; + /// @notice Creation codes Quark must ensure are deployed before executing this operation + bytes[] scriptSources; /// @notice Encoded function selector + arguments to invoke on the script contract bytes scriptCalldata; /// @notice Expiration time for the signature corresponding to this operation @@ -144,19 +137,12 @@ contract QuarkWallet is IERC1271 { revert SignatureExpired(); } - /* - * At most one of scriptAddress or scriptSource may be provided; - * specifying both adds cost (ie. wasted bytecode) for no benefit. - */ - if ((op.scriptAddress != address(0)) && (op.scriptSource.length > 0)) { - revert AmbiguousScript(); - } - - /* - * If scriptAddress is not given, scriptSource must be non-empty - */ - if (op.scriptAddress == address(0) && op.scriptSource.length == 0) { - revert EmptyCode(); + bytes memory encodedArray; + for (uint256 i = 0; i < op.scriptSources.length;) { + encodedArray = abi.encodePacked(encodedArray, keccak256(op.scriptSources[i])); + unchecked { + ++i; + } } bytes32 structHash = keccak256( @@ -164,7 +150,7 @@ contract QuarkWallet is IERC1271 { QUARK_OPERATION_TYPEHASH, op.nonce, op.scriptAddress, - keccak256(op.scriptSource), + keccak256(encodedArray), keccak256(op.scriptCalldata), op.expiry ) @@ -174,15 +160,17 @@ contract QuarkWallet is IERC1271 { // if the signature check does not revert, the signature is valid checkValidSignatureInternal(HasSignerExecutor(address(this)).signer(), digest, v, r, s); - // if scriptAddress not given, derive deterministic address from bytecode - address scriptAddress = op.scriptAddress; - if (scriptAddress == address(0)) { - scriptAddress = codeJar.saveCode(op.scriptSource); + // guarantee every script in scriptSources is deployed + for (uint256 i = 0; i < op.scriptSources.length;) { + codeJar.saveCode(op.scriptSources[i]); + unchecked { + ++i; + } } - emit ExecuteQuarkScript(msg.sender, scriptAddress, op.nonce, ExecutionType.Signature); + emit ExecuteQuarkScript(msg.sender, op.scriptAddress, op.nonce, ExecutionType.Signature); - return stateManager.setActiveNonceAndCallback(op.nonce, scriptAddress, op.scriptCalldata); + return stateManager.setActiveNonceAndCallback(op.nonce, op.scriptAddress, op.scriptCalldata); } /** diff --git a/test/ReplayableTransactions.t.sol b/test/ReplayableTransactions.t.sol index 844aa7c5..4e6ca0ff 100644 --- a/test/ReplayableTransactions.t.sol +++ b/test/ReplayableTransactions.t.sol @@ -30,7 +30,7 @@ contract ReplayableTransactionsTest is Test { address aliceAccount = vm.addr(alicePrivateKey); QuarkWallet aliceWallet; // see constructor() - bytes recurringPurchase = new YulHelper().getDeployed("RecurringPurchase.sol/RecurringPurchase.json"); + bytes recurringPurchase = new YulHelper().getCode("RecurringPurchase.sol/RecurringPurchase.json"); // Contracts address on mainnet address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; diff --git a/test/codejar/CodeJar.t.sol b/test/codejar/CodeJar.t.sol index 68e18f0a..b4b29abd 100644 --- a/test/codejar/CodeJar.t.sol +++ b/test/codejar/CodeJar.t.sol @@ -4,16 +4,24 @@ pragma solidity 0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; +import {YulHelper} from "test/lib/YulHelper.sol"; + import {Counter} from "test/lib/Counter.sol"; +import {TickCounter} from "test/lib/TickCounter.sol"; +import {Mememe} from "test/lib/Mememe.sol"; +import {ConstructorReverter} from "test/lib/ConstructorReverter.sol"; +import {Redeployer} from "test/lib/Redeployer.sol"; +import {Wacky, WackyBeacon, WackyCode, WackyFun} from "test/lib/Wacky.sol"; import {CodeJar} from "codejar/src/CodeJar.sol"; -import {CodeJarStub} from "codejar/src/CodeJarStub.sol"; contract CodeJarTest is Test { event Ping(uint256 value); CodeJar public codeJar; address destructingAddress; + WackyBeacon wackyBeacon; + address wackyAddress; bytes destructingCode = hex"6000ff"; // PUSH1 [60]; 0 [00]; SELFDESTRUCT [FF] constructor() { @@ -26,38 +34,43 @@ contract CodeJarTest is Test { // from a selfdestruct in forge, the selfdestruct must be done in the setUp. // See: https://github.com/foundry-rs/foundry/issues/1543 - destructingAddress = codeJar.saveCode(destructingCode); + destructingAddress = codeJar.saveCode(new YulHelper().stub(destructingCode)); assertEq(destructingAddress.code, destructingCode); (bool success,) = destructingAddress.call(hex""); assertEq(success, true); // Selfdestruct state changes do not take effect until after setUp assertEq(destructingAddress.code, destructingCode); - } - function testCodeJarInitCodeLength() public { - assertEq(type(CodeJarStub).creationCode.length, 20); + wackyBeacon = new WackyBeacon(); + wackyBeacon.setCode(type(WackyCode).runtimeCode); + wackyAddress = codeJar.saveCode(abi.encodePacked(type(Wacky).creationCode, abi.encode(wackyBeacon))); + assertEq(wackyAddress.code, type(WackyCode).runtimeCode); + assertEq(WackyCode(wackyAddress).hello(), 72); + WackyCode(wackyAddress).destruct(); } function testCodeJarSelfDestruct() public { assertEq(destructingAddress.code, hex""); assertEq(destructingAddress.codehash, 0); - assertEq(destructingAddress, codeJar.saveCode(destructingCode)); + assertEq(destructingAddress, codeJar.saveCode(new YulHelper().stub(destructingCode))); assertEq(destructingAddress.code, destructingCode); assertEq(destructingAddress.codehash, keccak256(destructingCode)); } function testCodeJarFirstDeploy() public { + bytes memory stubbed = new YulHelper().stub(hex"11223344"); uint256 gasLeft = gasleft(); - address scriptAddress = codeJar.saveCode(hex"11223344"); + address scriptAddress = codeJar.saveCode(stubbed); uint256 gasUsed = gasLeft - gasleft(); assertEq(scriptAddress.code, hex"11223344"); assertApproxEqAbs(gasUsed, 42000, 3000); } function testCodeJarDeployNotAffectedByChangedCodeHash() public { + // TODO: This test is more complex? vm.deal(address(0xbab), 10 ether); bytes memory code = hex"11223344"; - bytes memory initCode = abi.encodePacked(hex"63", uint32(code.length), hex"80600e6000396000f3", code); + bytes memory initCode = new YulHelper().stub(code); address targetAddress = address( uint160( uint256(keccak256(abi.encodePacked(bytes1(0xff), address(codeJar), uint256(0), keccak256(initCode)))) @@ -70,22 +83,24 @@ contract CodeJarTest is Test { assertNotEq(targetAddress.codehash, 0); uint256 gasLeft = gasleft(); // CodeJar will detect the codehash diff, but it will still be able to deploy the code - address scriptAddress = codeJar.saveCode(code); + address scriptAddress = codeJar.saveCode(initCode); uint256 gasUsed = gasLeft - gasleft(); assertEq(scriptAddress.code, code); assertApproxEqAbs(gasUsed, 40000, 3000); } function testCodeJarSecondDeploy() public { - address scriptAddress = codeJar.saveCode(hex"11223344"); + bytes memory stubbed = new YulHelper().stub(hex"11223344"); + + address scriptAddress = codeJar.saveCode(stubbed); uint256 gasLeft = gasleft(); - address scriptAddressNext = codeJar.saveCode(hex"11223344"); + address scriptAddressNext = codeJar.saveCode(stubbed); uint256 gasUsed = gasLeft - gasleft(); assertEq(scriptAddress, scriptAddressNext); assertEq(scriptAddressNext.code, hex"11223344"); - assertApproxEqAbs(gasUsed, 3000, 1000); + assertApproxEqAbs(gasUsed, 2000, 1000); } function testCodeJarInputVariety() public { @@ -101,10 +116,10 @@ contract CodeJarTest is Test { hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff11"; for (uint8 i = 0; i < scripts.length; i++) { - assertEq(codeJar.codeExists(scripts[i]), false); - address codeAddress = codeJar.saveCode(scripts[i]); + assertEq(codeJar.codeExists(new YulHelper().stub(scripts[i])), false); + address codeAddress = codeJar.saveCode(new YulHelper().stub(scripts[i])); assertEq(codeAddress.code, scripts[i]); - assertEq(codeJar.codeExists(scripts[i]), true); + assertEq(codeJar.codeExists(new YulHelper().stub(scripts[i])), true); } } @@ -125,16 +140,20 @@ contract CodeJarTest is Test { assertEq(address(0xaa).codehash, 0); - address zeroDeploy = codeJar.saveCode(hex""); - assertEq(zeroDeploy.codehash, keccak256(hex"")); + // This cannot deploy now + bytes memory stubbed = new YulHelper().stub(hex""); + vm.expectRevert(); + address zeroDeploy = codeJar.saveCode(stubbed); + assertEq(zeroDeploy.codehash, 0); assertEq(zeroDeploy.code, hex""); - address nonZeroDeploy = codeJar.saveCode(hex"00"); + address nonZeroDeploy = codeJar.saveCode(new YulHelper().stub(hex"00")); + assertEq(nonZeroDeploy.codehash, keccak256(hex"00")); assertEq(nonZeroDeploy.code, hex"00"); } function testCodeJarCounter() public { - address scriptAddress = codeJar.saveCode(type(Counter).runtimeCode); + address scriptAddress = codeJar.saveCode(type(Counter).creationCode); assertEq(scriptAddress.code, type(Counter).runtimeCode); Counter counter = Counter(scriptAddress); @@ -143,32 +162,98 @@ contract CodeJarTest is Test { assertEq(counter.number(), 1); } + function testCodeJarTickCounter() public { + address scriptAddress = codeJar.saveCode(abi.encodePacked(type(TickCounter).creationCode, abi.encode(55))); + + // Note: runtime code is modified by immutable + // assertEq(scriptAddress.code, type(Counter).runtimeCode); + + Counter counter = Counter(scriptAddress); + assertEq(counter.number(), 55); + counter.increment(); + assertEq(counter.number(), 56); + } + + function testCodeJarStoresSelfReference() public { + Mememe mememe = Mememe(codeJar.saveCode(type(Mememe).creationCode)); + + vm.expectRevert("it's me, mario"); + mememe.hello(); + + (bool success, bytes memory returnData) = address(mememe).delegatecall(abi.encodeCall(mememe.hello, ())); + assert(success == true); + + assertEq(55, abi.decode(returnData, (uint256))); + } + + function testCodeJarDeploysAnother() public { + Redeployer redeployer = Redeployer( + codeJar.saveCode( + abi.encodePacked( + type(Redeployer).creationCode, + abi.encode(abi.encodePacked(type(TickCounter).creationCode, abi.encode(62))) + ) + ) + ); + + TickCounter counter = TickCounter(redeployer.deployed()); + + assertEq(counter.number(), 62); + } + function testCodeJarLarge() public { bytes32[] memory script = new bytes32[](10000); bytes memory code = abi.encodePacked(script); - codeJar.saveCode(code); + codeJar.saveCode(new YulHelper().stub(code)); } - function testCodeJarDeployConstructor() public { - // This is the initCode used in CodeJar. It's a constructor code that returns "0xabcd". - bytes memory contructorByteCode = abi.encodePacked(hex"63", hex"00000002", hex"80600e6000396000f3", hex"abcd"); - address scriptAddress = codeJar.saveCode(contructorByteCode); + function testCodeJarRefusesToDeployEmptyCode() public { + bytes memory code = hex""; + assertEq(codeJar.codeExists(new YulHelper().stub(code)), false); + bytes memory stubbed = new YulHelper().stub(code); + vm.expectRevert(); + codeJar.saveCode(stubbed); + assertEq(codeJar.codeExists(new YulHelper().stub(code)), false); + } - (bool success, bytes memory returnData) = scriptAddress.call(hex""); + function testRevertsOnConstructorRevert() public { + vm.expectRevert(); + codeJar.saveCode(type(ConstructorReverter).creationCode); + assertEq(codeJar.codeExists(type(ConstructorReverter).creationCode), false); - assertEq(returnData, hex"abcd"); + vm.expectRevert(); + codeJar.saveCode(type(ConstructorReverter).creationCode); + assertEq(codeJar.codeExists(type(ConstructorReverter).creationCode), false); } - function testCodeJarCodeDoesNotExistOnEmptyScriptWithETH() public { - bytes memory code = hex""; - assertEq(codeJar.codeExists(code), false); - address scriptAddress = codeJar.saveCode(code); + function testCodeJarCanDeployCodeThatHadEthSent() public { + bytes memory code = hex"112233"; + assertEq(codeJar.codeExists(new YulHelper().stub(code)), false); + address codeAddress = codeJar.getCodeAddress(new YulHelper().stub(code)); vm.deal(address(this), 1 ether); - scriptAddress.call{value: 1}(""); + (bool success,) = codeAddress.call{value: 1}(""); + assertEq(success, true); // Ensure codeExists correctness holds for empty code with ETH - assertEq(codeJar.codeExists(code), false); + assertEq(codeJar.codeExists(new YulHelper().stub(code)), false); + assertEq(codeAddress.code, hex""); + + codeJar.saveCode(new YulHelper().stub(code)); + + assertEq(codeJar.codeExists(new YulHelper().stub(code)), true); + assertEq(codeAddress.code, code); } - // Note: cannot test code too large, as overflow impossible to test + function testCodeJarCanBeWacky() public { + wackyBeacon.setCode(type(WackyFun).runtimeCode); + codeJar.saveCode(abi.encodePacked(type(Wacky).creationCode, abi.encode(wackyBeacon))); + assertEq(wackyAddress.code, type(WackyFun).runtimeCode); + assertEq(WackyFun(wackyAddress).cool(), 88); + } + + function testCodeJarOnSelfDestructingConstructor() public { + vm.expectRevert(); + codeJar.saveCode(abi.encodePacked(type(WackyCode).creationCode)); + assertEq(codeJar.codeExists(abi.encodePacked(type(WackyCode).creationCode)), false); + } } diff --git a/test/legend-scripts/ApproveAndSwap.t.sol b/test/legend-scripts/ApproveAndSwap.t.sol index 22339e4e..c7f87dd9 100644 --- a/test/legend-scripts/ApproveAndSwap.t.sol +++ b/test/legend-scripts/ApproveAndSwap.t.sol @@ -42,7 +42,7 @@ contract ApproveAndSwapTest is Test { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - bytes memory legendScript = new YulHelper().getDeployed("LegendScript.sol/ApproveAndSwap.json"); + bytes memory legendScript = new YulHelper().getCode("LegendScript.sol/ApproveAndSwap.json"); deal(USDC, address(wallet), 1_000_000e6); uint256 sellAmount = 1_000e6; @@ -73,7 +73,7 @@ contract ApproveAndSwapTest is Test { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - bytes memory legendScript = new YulHelper().getDeployed("LegendScript.sol/ApproveAndSwap.json"); + bytes memory legendScript = new YulHelper().getCode("LegendScript.sol/ApproveAndSwap.json"); deal(USDC, address(wallet), 1_000_000e6); @@ -107,7 +107,7 @@ contract ApproveAndSwapTest is Test { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - bytes memory legendScript = new YulHelper().getDeployed("LegendScript.sol/ApproveAndSwap.json"); + bytes memory legendScript = new YulHelper().getCode("LegendScript.sol/ApproveAndSwap.json"); deal(USDC, address(wallet), 1_000_000e6); diff --git a/test/legend-scripts/CometClaimRewards.t.sol b/test/legend-scripts/CometClaimRewards.t.sol index a4266961..3143828d 100644 --- a/test/legend-scripts/CometClaimRewards.t.sol +++ b/test/legend-scripts/CometClaimRewards.t.sol @@ -50,7 +50,7 @@ contract CometClaimRewardsTest is Test { function testClaimComp() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - bytes memory legendScript = new YulHelper().getDeployed("LegendScript.sol/CometClaimRewards.json"); + bytes memory legendScript = new YulHelper().getCode("LegendScript.sol/CometClaimRewards.json"); deal(USDC, address(wallet), 1_000_000e6); diff --git a/test/legend-scripts/CometRepayAndWithdrawMultipleAssets.t.sol b/test/legend-scripts/CometRepayAndWithdrawMultipleAssets.t.sol index eb9f4852..d3724352 100644 --- a/test/legend-scripts/CometRepayAndWithdrawMultipleAssets.t.sol +++ b/test/legend-scripts/CometRepayAndWithdrawMultipleAssets.t.sol @@ -50,8 +50,7 @@ contract CometRepayAndWithdrawMultipleAssetsTest is Test { function testRepayAndWithdrawMultipleAssets() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - bytes memory legendScript = - new YulHelper().getDeployed("LegendScript.sol/CometRepayAndWithdrawMultipleAssets.json"); + bytes memory legendScript = new YulHelper().getCode("LegendScript.sol/CometRepayAndWithdrawMultipleAssets.json"); deal(WETH, address(wallet), 10 ether); deal(LINK, address(wallet), 10e18); @@ -89,8 +88,7 @@ contract CometRepayAndWithdrawMultipleAssetsTest is Test { function testInvalidInput() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - bytes memory legendScript = - new YulHelper().getDeployed("LegendScript.sol/CometRepayAndWithdrawMultipleAssets.json"); + bytes memory legendScript = new YulHelper().getCode("LegendScript.sol/CometRepayAndWithdrawMultipleAssets.json"); address[] memory assets = new address[](2); uint256[] memory amounts = new uint256[](1); diff --git a/test/legend-scripts/CometSupplyActions.t.sol b/test/legend-scripts/CometSupplyActions.t.sol index 96917384..e48acdae 100644 --- a/test/legend-scripts/CometSupplyActions.t.sol +++ b/test/legend-scripts/CometSupplyActions.t.sol @@ -35,7 +35,7 @@ contract SupplyActionsTest is Test { address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant LINK = 0x514910771AF9Ca656af840dff83E8264EcF986CA; - bytes legendScript = new YulHelper().getDeployed("LegendScript.sol/CometSupplyActions.json"); + bytes legendScript = new YulHelper().getCode("LegendScript.sol/CometSupplyActions.json"); function setUp() public { // Fork setup diff --git a/test/legend-scripts/CometSupplyMultipleAssetsAndBorrow.t.sol b/test/legend-scripts/CometSupplyMultipleAssetsAndBorrow.t.sol index 310c4563..fce6f150 100644 --- a/test/legend-scripts/CometSupplyMultipleAssetsAndBorrow.t.sol +++ b/test/legend-scripts/CometSupplyMultipleAssetsAndBorrow.t.sol @@ -50,8 +50,7 @@ contract CometSupplyMultipleAssetsAndBorrowTest is Test { function testSupplyMultipleAssetsAndBorrow() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - bytes memory legendScript = - new YulHelper().getDeployed("LegendScript.sol/CometSupplyMultipleAssetsAndBorrow.json"); + bytes memory legendScript = new YulHelper().getCode("LegendScript.sol/CometSupplyMultipleAssetsAndBorrow.json"); deal(WETH, address(wallet), 10 ether); deal(LINK, address(wallet), 10e18); @@ -84,8 +83,7 @@ contract CometSupplyMultipleAssetsAndBorrowTest is Test { function testInvalidInput() public { vm.pauseGasMetering(); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); - bytes memory legendScript = - new YulHelper().getDeployed("LegendScript.sol/CometSupplyMultipleAssetsAndBorrow.json"); + bytes memory legendScript = new YulHelper().getCode("LegendScript.sol/CometSupplyMultipleAssetsAndBorrow.json"); address[] memory assets = new address[](2); uint256[] memory amounts = new uint256[](1); diff --git a/test/legend-scripts/CometWithdrawActions.t.sol b/test/legend-scripts/CometWithdrawActions.t.sol index 276d061b..ce5c15f6 100644 --- a/test/legend-scripts/CometWithdrawActions.t.sol +++ b/test/legend-scripts/CometWithdrawActions.t.sol @@ -35,7 +35,7 @@ contract WithdrawActionsTest is Test { address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant LINK = 0x514910771AF9Ca656af840dff83E8264EcF986CA; - bytes legendScript = new YulHelper().getDeployed("LegendScript.sol/CometWithdrawActions.json"); + bytes legendScript = new YulHelper().getCode("LegendScript.sol/CometWithdrawActions.json"); function setUp() public { // Fork setup diff --git a/test/legend-scripts/GetDrip.t.sol b/test/legend-scripts/GetDrip.t.sol index 413d297b..1ffee536 100644 --- a/test/legend-scripts/GetDrip.t.sol +++ b/test/legend-scripts/GetDrip.t.sol @@ -36,7 +36,7 @@ contract GetDripTest is Test { QuarkWallet wallet = QuarkWallet(factory.create(alice, alice)); new YulHelper().deploy("GetDrip.sol/GetDrip.json"); - bytes memory legendScript = new YulHelper().getDeployed("GetDrip.sol/GetDrip.json"); + bytes memory legendScript = new YulHelper().getCode("GetDrip.sol/GetDrip.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( wallet, legendScript, abi.encodeCall(GetDrip.drip, (USDC)), ScriptType.ScriptSource diff --git a/test/legend-scripts/TransferActions.t.sol b/test/legend-scripts/TransferActions.t.sol index 7e0706dd..06b5a642 100644 --- a/test/legend-scripts/TransferActions.t.sol +++ b/test/legend-scripts/TransferActions.t.sol @@ -39,9 +39,9 @@ contract TransferActionsTest is Test { address alice = vm.addr(alicePrivateKey); uint256 bobPrivateKey = 0xb0b; address bob = vm.addr(bobPrivateKey); - bytes legendScript = new YulHelper().getDeployed("LegendScript.sol/TransferActions.json"); - bytes multicall = new YulHelper().getDeployed("Multicall.sol/Multicall.json"); - bytes allowCallbacks = new YulHelper().getDeployed("AllowCallbacks.sol/AllowCallbacks.json"); + bytes legendScript = new YulHelper().getCode("LegendScript.sol/TransferActions.json"); + bytes multicall = new YulHelper().getCode("Multicall.sol/Multicall.json"); + bytes allowCallbacks = new YulHelper().getCode("AllowCallbacks.sol/AllowCallbacks.json"); // Contracts address on mainnet address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; @@ -146,7 +146,7 @@ contract TransferActionsTest is Test { function testTransferReentrancyAttackSuccessWithCallbackEnabled() public { vm.pauseGasMetering(); - bytes memory reentrantTransfer = new YulHelper().getDeployed("ReentrantTransfer.sol/ReentrantTransfer.json"); + bytes memory reentrantTransfer = new YulHelper().getCode("ReentrantTransfer.sol/ReentrantTransfer.json"); address allowCallbacksAddress = codeJar.saveCode(allowCallbacks); address reentrantTransferAddress = codeJar.saveCode(reentrantTransfer); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); @@ -182,7 +182,7 @@ contract TransferActionsTest is Test { function testTransferERC777TokenReentrancyAttackSuccessWithCallbackEnabled() public { vm.pauseGasMetering(); - bytes memory reentrantTransfer = new YulHelper().getDeployed("ReentrantTransfer.sol/ReentrantTransfer.json"); + bytes memory reentrantTransfer = new YulHelper().getCode("ReentrantTransfer.sol/ReentrantTransfer.json"); address allowCallbacksAddress = codeJar.saveCode(allowCallbacks); address reentrantTransferAddress = codeJar.saveCode(reentrantTransfer); QuarkWallet wallet = QuarkWallet(factory.create(alice, address(0))); diff --git a/test/legend-scripts/UniswapSwapActions.t.sol b/test/legend-scripts/UniswapSwapActions.t.sol index bbe18beb..295b17d4 100644 --- a/test/legend-scripts/UniswapSwapActions.t.sol +++ b/test/legend-scripts/UniswapSwapActions.t.sol @@ -37,7 +37,7 @@ contract UniswapSwapActionsTest is Test { address constant COMP = 0xc00e94Cb662C3520282E6f5717214004A7f26888; // Uniswap router info on mainnet address constant uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564; - bytes legendScript = new YulHelper().getDeployed("LegendScript.sol/UniswapSwapActions.json"); + bytes legendScript = new YulHelper().getCode("LegendScript.sol/UniswapSwapActions.json"); function setUp() public { // Fork setup diff --git a/test/lib/ConstructorReverter.sol b/test/lib/ConstructorReverter.sol new file mode 100644 index 00000000..9ed871b2 --- /dev/null +++ b/test/lib/ConstructorReverter.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +contract ConstructorReverter { + error Test(uint256); + + constructor() { + revert Test(55); + } +} diff --git a/test/lib/EmptyCode.sol b/test/lib/EmptyCode.sol new file mode 100644 index 00000000..c27562ca --- /dev/null +++ b/test/lib/EmptyCode.sol @@ -0,0 +1,10 @@ +pragma solidity "0.8.23"; + +contract EmptyCode { + // NOTE: force the solidity compiler to produce empty code when this is deployed + constructor() { + assembly { + return(0x0, 0) + } + } +} diff --git a/test/lib/Mememe.sol b/test/lib/Mememe.sol new file mode 100644 index 00000000..50afd81a --- /dev/null +++ b/test/lib/Mememe.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +contract Mememe { + address public immutable me; + + constructor() { + me = address(this); + } + + function hello() public view returns (uint256) { + require(address(this) != me, "it's me, mario"); + return 55; + } +} diff --git a/test/lib/Ping.yul0 b/test/lib/Ping.yul0 new file mode 100644 index 00000000..7825db28 --- /dev/null +++ b/test/lib/Ping.yul0 @@ -0,0 +1,7 @@ +object "Ping" { + code { + mstore(0x00, calldataload(0x00)) + log1(0x00, 0x20, 0x48257dc961b6f792c2b78a080dacfed693b660960a702de21cee364e20270e2f) /* cast sig-event "Ping(uint256)" */ + return(0x00, 0x00) + } +} diff --git a/test/lib/QuarkOperationHelper.sol b/test/lib/QuarkOperationHelper.sol index 13af72ce..f06649b2 100644 --- a/test/lib/QuarkOperationHelper.sol +++ b/test/lib/QuarkOperationHelper.sol @@ -9,12 +9,14 @@ enum ScriptType { ScriptSource } +// 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 { function newBasicOp(QuarkWallet wallet, bytes memory scriptSource, ScriptType scriptType) external returns (QuarkWallet.QuarkOperation memory) { - return newBasicOpWithCalldata(wallet, scriptSource, abi.encode(), scriptType); + return newBasicOpWithCalldata(wallet, scriptSource, abi.encode(), new bytes[](0), scriptType); } function newBasicOpWithCalldata( @@ -22,20 +24,30 @@ contract QuarkOperationHelper is Test { bytes memory scriptSource, bytes memory scriptCalldata, ScriptType scriptType + ) public returns (QuarkWallet.QuarkOperation memory) { + return newBasicOpWithCalldata(wallet, scriptSource, scriptCalldata, new bytes[](0), scriptType); + } + + function newBasicOpWithCalldata( + QuarkWallet wallet, + bytes memory scriptSource, + bytes memory scriptCalldata, + bytes[] memory ensureScripts, + ScriptType scriptType ) public returns (QuarkWallet.QuarkOperation memory) { address scriptAddress = wallet.codeJar().saveCode(scriptSource); if (scriptType == ScriptType.ScriptAddress) { return QuarkWallet.QuarkOperation({ scriptAddress: scriptAddress, - scriptSource: "", + scriptSources: ensureScripts, scriptCalldata: scriptCalldata, nonce: wallet.stateManager().nextNonce(address(wallet)), expiry: block.timestamp + 1000 }); } else { return QuarkWallet.QuarkOperation({ - scriptAddress: address(0), - scriptSource: scriptSource, + scriptAddress: scriptAddress, + scriptSources: ensureScripts, scriptCalldata: scriptCalldata, nonce: wallet.stateManager().nextNonce(address(wallet)), expiry: block.timestamp + 1000 diff --git a/test/lib/Redeployer.sol b/test/lib/Redeployer.sol new file mode 100644 index 00000000..06ef26cc --- /dev/null +++ b/test/lib/Redeployer.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +interface CodeJar { + function saveCode(bytes memory code) external returns (address); +} + +contract Redeployer { + address public immutable deployed; + + constructor(bytes memory deploy) { + deployed = CodeJar(msg.sender).saveCode(deploy); + } +} diff --git a/test/lib/Reverts.sol b/test/lib/Reverts.sol index d723a672..0cebf8c6 100644 --- a/test/lib/Reverts.sol +++ b/test/lib/Reverts.sol @@ -6,11 +6,11 @@ import {CodeJar} from "codejar/src/CodeJar.sol"; contract Reverts { error Whoops(); - function divideByZero() external { + function divideByZero() external pure { uint256(100) / uint256(0); } - function revertSeven() external { + function revertSeven() external pure { uint256 p; assembly { p := mload(0x40) // free-memory pointer @@ -20,7 +20,7 @@ contract Reverts { } } - function outOfGas() external { + function outOfGas() external view { while (gasleft() >= 0) {} } diff --git a/test/lib/SignatureHelper.sol b/test/lib/SignatureHelper.sol index b63636ff..decb25a2 100644 --- a/test/lib/SignatureHelper.sol +++ b/test/lib/SignatureHelper.sol @@ -26,13 +26,21 @@ contract SignatureHelper is Test { return vm.sign(privateKey, digest); } - function structHash(QuarkWallet.QuarkOperation memory op) internal pure returns (bytes32) { + function structHash(QuarkWallet.QuarkOperation memory op) public pure returns (bytes32) { + bytes memory encodedArray; + for (uint256 i = 0; i < op.scriptSources.length;) { + encodedArray = abi.encodePacked(encodedArray, keccak256(op.scriptSources[i])); + unchecked { + ++i; + } + } + return keccak256( abi.encode( QuarkWalletMetadata.QUARK_OPERATION_TYPEHASH, op.nonce, op.scriptAddress, - keccak256(op.scriptSource), + keccak256(encodedArray), keccak256(op.scriptCalldata), op.expiry ) diff --git a/test/lib/TickCounter.sol b/test/lib/TickCounter.sol new file mode 100644 index 00000000..2c307690 --- /dev/null +++ b/test/lib/TickCounter.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +contract TickCounter { + uint256 public immutable base; + uint256 private number_; + + constructor(uint256 base_) { + base = base_; + } + + function number() public view returns (uint256) { + return base + number_; + } + + function setNumber(uint256 newNumber) public { + number_ = newNumber; + } + + function increment() public { + number_++; + } + + function increment(uint256 n) public { + number_ += n; + } + + function decrement(uint256 n) public returns (uint256) { + number_ -= n; + return number(); + } +} diff --git a/test/lib/Wacky.sol b/test/lib/Wacky.sol new file mode 100644 index 00000000..86423272 --- /dev/null +++ b/test/lib/Wacky.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.23; + +contract WackyBeacon { + bytes public code; + + function setCode(bytes memory code_) external { + code = code_; + } +} + +contract WackyCode { + constructor() { + assembly { + selfdestruct(origin()) + } + } + + function hello() external pure returns (uint256) { + return 72; + } + + function destruct() external { + assembly { + selfdestruct(origin()) + } + } +} + +contract WackyFun { + function cool() external pure returns (uint256) { + return 88; + } +} + +contract Wacky { + error Test(uint256); + + constructor(WackyBeacon beacon) { + bytes memory code = beacon.code(); + + assembly { + return(add(code, 0x20), mload(code)) + } + } +} diff --git a/test/lib/YulHelper.sol b/test/lib/YulHelper.sol index 0ca6166d..6c912f53 100644 --- a/test/lib/YulHelper.sol +++ b/test/lib/YulHelper.sol @@ -23,11 +23,17 @@ contract YulHelper is Test { return deployedAddress; } - function get(string memory jsonFileName) public view returns (bytes memory) { + function getCode(string memory jsonFileName) public view returns (bytes memory) { return vm.getCode(string.concat("out/", jsonFileName)); } function getDeployed(string memory jsonFileName) public view returns (bytes memory) { return vm.getDeployedCode(string.concat("out/", jsonFileName)); } + + /// EVM opcodes to simply return the code as a very simple `initCode` / "constructor" + function stub(bytes memory code) public pure returns (bytes memory) { + uint32 codeLen = uint32(code.length); + return abi.encodePacked(hex"63", codeLen, hex"80600e6000396000f3", code); + } } diff --git a/test/quark-core-scripts/ConditionalMulticall.t.sol b/test/quark-core-scripts/ConditionalMulticall.t.sol index 73ea2232..a993bbc3 100644 --- a/test/quark-core-scripts/ConditionalMulticall.t.sol +++ b/test/quark-core-scripts/ConditionalMulticall.t.sol @@ -37,8 +37,8 @@ contract ConditionalMulticallTest is Test { address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // Uniswap router info on mainnet address constant uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564; - bytes ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); - bytes conditionalMulticall = new YulHelper().getDeployed("ConditionalMulticall.sol/ConditionalMulticall.json"); + bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); + bytes conditionalMulticall = new YulHelper().getCode("ConditionalMulticall.sol/ConditionalMulticall.json"); address ethcallAddress; function setUp() public { diff --git a/test/quark-core-scripts/Ethcall.t.sol b/test/quark-core-scripts/Ethcall.t.sol index a1f01e0a..3345a28c 100644 --- a/test/quark-core-scripts/Ethcall.t.sol +++ b/test/quark-core-scripts/Ethcall.t.sol @@ -36,7 +36,7 @@ contract EthcallTest is Test { address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - bytes ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); + bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); function setUp() public { // Fork setup diff --git a/test/quark-core-scripts/Multicall.t.sol b/test/quark-core-scripts/Multicall.t.sol index d8844cd8..48fcf7c8 100644 --- a/test/quark-core-scripts/Multicall.t.sol +++ b/test/quark-core-scripts/Multicall.t.sol @@ -37,14 +37,14 @@ contract MulticallTest is Test { address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // Uniswap router info on mainnet address constant uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564; - bytes multicall = new YulHelper().getDeployed("Multicall.sol/Multicall.json"); - bytes ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); + bytes multicall = new YulHelper().getCode("Multicall.sol/Multicall.json"); + bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); - bytes legendCometSupplyScript = new YulHelper().getDeployed("LegendScript.sol/CometSupplyActions.json"); + bytes legendCometSupplyScript = new YulHelper().getCode("LegendScript.sol/CometSupplyActions.json"); - bytes legendCometWithdrawScript = new YulHelper().getDeployed("LegendScript.sol/CometWithdrawActions.json"); + bytes legendCometWithdrawScript = new YulHelper().getCode("LegendScript.sol/CometWithdrawActions.json"); - bytes legendUniswapSwapScript = new YulHelper().getDeployed("LegendScript.sol/UniswapSwapActions.json"); + bytes legendUniswapSwapScript = new YulHelper().getCode("LegendScript.sol/UniswapSwapActions.json"); address ethcallAddress; address multicallAddress; diff --git a/test/quark-core-scripts/UniswapFlashLoan.t.sol b/test/quark-core-scripts/UniswapFlashLoan.t.sol index 63979a99..1d49e43e 100644 --- a/test/quark-core-scripts/UniswapFlashLoan.t.sol +++ b/test/quark-core-scripts/UniswapFlashLoan.t.sol @@ -42,9 +42,9 @@ contract UniswapFlashLoanTest is Test { address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // Router info on mainnet address constant uniswapRouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564; - bytes multicall = new YulHelper().getDeployed("Multicall.sol/Multicall.json"); - bytes ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); - bytes uniswapFlashLoan = new YulHelper().getDeployed("UniswapFlashLoan.sol/UniswapFlashLoan.json"); + bytes multicall = new YulHelper().getCode("Multicall.sol/Multicall.json"); + bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); + bytes uniswapFlashLoan = new YulHelper().getCode("UniswapFlashLoan.sol/UniswapFlashLoan.json"); address ethcallAddress; address multicallAddress; address uniswapFlashLoanAddress; diff --git a/test/quark-core-scripts/UniswapFlashSwapExactOut.t.sol b/test/quark-core-scripts/UniswapFlashSwapExactOut.t.sol index df6b9e8e..1ec5786c 100644 --- a/test/quark-core-scripts/UniswapFlashSwapExactOut.t.sol +++ b/test/quark-core-scripts/UniswapFlashSwapExactOut.t.sol @@ -37,10 +37,10 @@ contract UniswapFlashSwapExactOutTest is Test { address constant comet = 0xc3d688B66703497DAA19211EEdff47f25384cdc3; address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - bytes multicall = new YulHelper().getDeployed("Multicall.sol/Multicall.json"); - bytes ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); + bytes multicall = new YulHelper().getCode("Multicall.sol/Multicall.json"); + bytes ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); bytes uniswapFlashSwapExactOut = - new YulHelper().getDeployed("UniswapFlashSwapExactOut.sol/UniswapFlashSwapExactOut.json"); + new YulHelper().getCode("UniswapFlashSwapExactOut.sol/UniswapFlashSwapExactOut.json"); address ethcallAddress; address multicallAddress; address uniswapFlashSwapExactOutAddress; diff --git a/test/quark-core/Callbacks.t.sol b/test/quark-core/Callbacks.t.sol index 27e052d5..e84f19df 100644 --- a/test/quark-core/Callbacks.t.sol +++ b/test/quark-core/Callbacks.t.sol @@ -55,8 +55,7 @@ contract CallbacksTest is Test { vm.pauseGasMetering(); assertEq(counter.number(), 0); - bytes memory callbackFromCounter = - new YulHelper().getDeployed("CallbackFromCounter.sol/CallbackFromCounter.json"); + bytes memory callbackFromCounter = new YulHelper().getCode("CallbackFromCounter.sol/CallbackFromCounter.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, @@ -81,8 +80,7 @@ contract CallbacksTest is Test { assertEq(address(counter).balance, 1000 wei); assertEq(address(aliceWallet).balance, 0 wei); - bytes memory callbackFromCounter = - new YulHelper().getDeployed("CallbackFromCounter.sol/CallbackFromCounter.json"); + bytes memory callbackFromCounter = new YulHelper().getCode("CallbackFromCounter.sol/CallbackFromCounter.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, @@ -105,10 +103,9 @@ contract CallbacksTest is Test { function testAllowNestedCallbacks() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory callbackFromCounter = - new YulHelper().getDeployed("CallbackFromCounter.sol/CallbackFromCounter.json"); + bytes memory callbackFromCounter = new YulHelper().getCode("CallbackFromCounter.sol/CallbackFromCounter.json"); bytes memory executeOtherScript = - new YulHelper().getDeployed("ExecuteOtherOperation.sol/ExecuteOtherOperation.json"); + new YulHelper().getCode("ExecuteOtherOperation.sol/ExecuteOtherOperation.json"); QuarkWallet.QuarkOperation memory nestedOp = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, @@ -141,9 +138,9 @@ contract CallbacksTest is Test { vm.pauseGasMetering(); assertEq(counter.number(), 0); - bytes memory counterScript = new YulHelper().getDeployed("CounterScript.sol/CounterScript.json"); + bytes memory counterScript = new YulHelper().getCode("CounterScript.sol/CounterScript.json"); bytes memory executeOtherScript = - new YulHelper().getDeployed("ExecuteOtherOperation.sol/ExecuteOtherOperation.json"); + new YulHelper().getCode("ExecuteOtherOperation.sol/ExecuteOtherOperation.json"); QuarkWallet.QuarkOperation memory nestedOp = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, counterScript, abi.encodeWithSignature("run(address)", counter), ScriptType.ScriptAddress @@ -172,7 +169,7 @@ contract CallbacksTest is Test { // gas: do not meter set-up vm.pauseGasMetering(); bytes32 callbackKey = aliceWallet.CALLBACK_KEY(); - bytes memory allowCallbacks = new YulHelper().getDeployed("AllowCallbacks.sol/AllowCallbacks.json"); + bytes memory allowCallbacks = new YulHelper().getCode("AllowCallbacks.sol/AllowCallbacks.json"); QuarkWallet.QuarkOperation memory op1 = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, allowCallbacks, abi.encodeWithSignature("allowCallbackAndReplay()"), ScriptType.ScriptSource @@ -199,7 +196,7 @@ contract CallbacksTest is Test { function testRevertsOnCallbackWhenNoActiveCallback() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); + bytes memory ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, @@ -256,8 +253,8 @@ contract CallbacksTest is Test { * recursive callbacks and exploiting the wallet. */ vm.pauseGasMetering(); - bytes memory exploitableScript = new YulHelper().getDeployed("CallcodeReentrancy.sol/ExploitableScript.json"); - bytes memory callbackCaller = new YulHelper().getDeployed("CallcodeReentrancy.sol/CallbackCaller.json"); + bytes memory exploitableScript = new YulHelper().getCode("CallcodeReentrancy.sol/ExploitableScript.json"); + bytes memory callbackCaller = new YulHelper().getCode("CallcodeReentrancy.sol/CallbackCaller.json"); address callbackCallerAddress = codeJar.saveCode(callbackCaller); @@ -285,8 +282,8 @@ contract CallbacksTest is Test { function testCallcodeReentrancyProtectionWithProtectedScript() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory protectedScript = new YulHelper().getDeployed("CallcodeReentrancy.sol/ProtectedScript.json"); - bytes memory callbackCaller = new YulHelper().getDeployed("CallcodeReentrancy.sol/CallbackCaller.json"); + bytes memory protectedScript = new YulHelper().getCode("CallcodeReentrancy.sol/ProtectedScript.json"); + bytes memory callbackCaller = new YulHelper().getCode("CallcodeReentrancy.sol/CallbackCaller.json"); address callbackCallerAddress = codeJar.saveCode(callbackCaller); @@ -338,8 +335,8 @@ contract CallbacksTest is Test { function testCallcodeReentrancyExploitWithProtectedScript() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory exploitableScript = new YulHelper().getDeployed("CallcodeReentrancy.sol/ExploitableScript.json"); - bytes memory callbackCaller = new YulHelper().getDeployed("CallcodeReentrancy.sol/CallbackCaller.json"); + bytes memory exploitableScript = new YulHelper().getCode("CallcodeReentrancy.sol/ExploitableScript.json"); + bytes memory callbackCaller = new YulHelper().getCode("CallcodeReentrancy.sol/CallbackCaller.json"); address callbackCallerAddress = codeJar.saveCode(callbackCaller); diff --git a/test/quark-core/EIP1271.t.sol b/test/quark-core/EIP1271.t.sol index d21b5868..222a4ab4 100644 --- a/test/quark-core/EIP1271.t.sol +++ b/test/quark-core/EIP1271.t.sol @@ -42,7 +42,7 @@ contract EIP1271Test is Test { } function incrementCounterOperation(QuarkWallet targetWallet) public returns (QuarkWallet.QuarkOperation memory) { - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); return new QuarkOperationHelper().newBasicOpWithCalldata( targetWallet, diff --git a/test/quark-core/EIP712.t.sol b/test/quark-core/EIP712.t.sol index 22693b32..82657bfc 100644 --- a/test/quark-core/EIP712.t.sol +++ b/test/quark-core/EIP712.t.sol @@ -46,7 +46,7 @@ contract EIP712Test is Test { } function incrementCounterOperation(QuarkWallet targetWallet) public returns (QuarkWallet.QuarkOperation memory) { - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); return new QuarkOperationHelper().newBasicOpWithCalldata( targetWallet, @@ -84,7 +84,8 @@ contract EIP712Test is Test { (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, wallet, op); // bad actor modifies script source to selfdestruct the wallet - op.scriptSource = hex"6000ff"; + op.scriptSources = new bytes[](1); + op.scriptSources[0] = bytes(hex"6000ff"); // gas: meter execute vm.resumeGasMetering(); @@ -100,6 +101,66 @@ contract EIP712Test is Test { assertEq(stateManager.isNonceSet(address(wallet), op.nonce), false); } + function testStructHash() public { + // gas: do not meter set-up + vm.pauseGasMetering(); + assertEq(counter.number(), 0); + + address wallet_ = address(0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9); + bytes memory incrementer = + hex"608060405234801561001057600080fd5b506102a7806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80636b582b7614610056578063e5910ae714610069575b73f62849f9a0b5bf2913b396098f7c7019b51a820a61005481610077565b005b610054610064366004610230565b610173565b610054610077366004610230565b806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156100b257600080fd5b505af11580156100c6573d6000803e3d6000fd5b50505050806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561010557600080fd5b505af1158015610119573d6000803e3d6000fd5b50505050806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561015857600080fd5b505af115801561016c573d6000803e3d6000fd5b5050505050565b61017c81610077565b306001600160a01b0316632e716fb16040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101de9190610254565b6001600160a01b0316631913592a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561015857600080fd5b6001600160a01b038116811461022d57600080fd5b50565b60006020828403121561024257600080fd5b813561024d81610218565b9392505050565b60006020828403121561026657600080fd5b815161024d8161021856fea26469706673582212200d71f9cd831b3c67d6f6131f807ee7fc47d21f07fe8f7b90a01dab56abb8403464736f6c63430008170033"; + address incrementerAddress = address(0x5cB7957c702bB6BB8F22aCcf66657F0defd4550b); + + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = incrementer; + uint96 nextNonce = 0; + bytes memory scriptCalldata = abi.encodeWithSignature("incrementCounter(address)", counter); + + assertEq(scriptCalldata, hex"e5910ae7000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a"); + assertEq(block.chainid, 31337); + + QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ + nonce: nextNonce, + scriptAddress: incrementerAddress, + scriptSources: scriptSources, + scriptCalldata: scriptCalldata, + expiry: 9999999999999 + }); + + /* + ethers.TypedDataEncoder.encode( + { + name: 'Quark Wallet', + version: '1', + chainId: 31337, + verifyingContract: '0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9' + }, + { QuarkOperation: [ + { name: 'nonce', type: 'uint96' }, + { name: 'scriptAddress', type: 'address' }, + { name: 'scriptSources', type: 'bytes[]' }, + { name: 'scriptCalldata', type: 'bytes' }, + { name: 'expiry', type: 'uint256' } + ]}, + { + nonce: 0, + scriptAddress: '0x5cB7957c702bB6BB8F22aCcf66657F0defd4550b', + scriptSources: ['0x608060405234801561001057600080fd5b506102a7806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80636b582b7614610056578063e5910ae714610069575b73f62849f9a0b5bf2913b396098f7c7019b51a820a61005481610077565b005b610054610064366004610230565b610173565b610054610077366004610230565b806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156100b257600080fd5b505af11580156100c6573d6000803e3d6000fd5b50505050806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561010557600080fd5b505af1158015610119573d6000803e3d6000fd5b50505050806001600160a01b031663d09de08a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561015857600080fd5b505af115801561016c573d6000803e3d6000fd5b5050505050565b61017c81610077565b306001600160a01b0316632e716fb16040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101de9190610254565b6001600160a01b0316631913592a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561015857600080fd5b6001600160a01b038116811461022d57600080fd5b50565b60006020828403121561024257600080fd5b813561024d81610218565b9392505050565b60006020828403121561026657600080fd5b815161024d8161021856fea26469706673582212200d71f9cd831b3c67d6f6131f807ee7fc47d21f07fe8f7b90a01dab56abb8403464736f6c63430008170033'], + scriptCalldata: '0xe5910ae7000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a', + expiry: 9999999999999 + } + ) + + 0x1901ce5fced5138ae147492ff6ba56247e9d6f30bbbe45ae60eb0a0135d528a94be4aa19c4de25dfba6a38836420cc4ecf14048cee3f258a3329bfeb40856daf159b + */ + + bytes32 domainHash = new SignatureHelper().domainSeparator(wallet_); + assertEq(domainHash, hex"ce5fced5138ae147492ff6ba56247e9d6f30bbbe45ae60eb0a0135d528a94be4"); + + bytes32 structHash = new SignatureHelper().structHash(op); + assertEq(structHash, hex"aa19c4de25dfba6a38836420cc4ecf14048cee3f258a3329bfeb40856daf159b"); + } + function testRevertsForBadCalldata() public { // gas: do not meter set-up vm.pauseGasMetering(); @@ -214,7 +275,7 @@ contract EIP712Test is Test { function testNonceIsNotSetForReplayableOperation() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); assertEq(counter.number(), 0); @@ -251,9 +312,9 @@ contract EIP712Test is Test { function testRevertBadRequirements() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); bytes memory executeWithRequirements = - new YulHelper().getDeployed("ExecuteWithRequirements.sol/ExecuteWithRequirements.json"); + new YulHelper().getCode("ExecuteWithRequirements.sol/ExecuteWithRequirements.json"); address incrementerAddress = codeJar.saveCode(incrementer); @@ -290,9 +351,9 @@ contract EIP712Test is Test { function testRequirements() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); bytes memory executeWithRequirements = - new YulHelper().getDeployed("ExecuteWithRequirements.sol/ExecuteWithRequirements.json"); + new YulHelper().getCode("ExecuteWithRequirements.sol/ExecuteWithRequirements.json"); address incrementerAddress = codeJar.saveCode(incrementer); diff --git a/test/quark-core/Executor.t.sol b/test/quark-core/Executor.t.sol index ee57473f..b32cb61d 100644 --- a/test/quark-core/Executor.t.sol +++ b/test/quark-core/Executor.t.sol @@ -51,10 +51,10 @@ contract ExecutorTest is Test { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); + bytes memory ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); address ethcallAddress = codeJar.saveCode(ethcall); - bytes memory executeOnBehalf = new YulHelper().getDeployed("ExecuteOnBehalf.sol/ExecuteOnBehalf.json"); + bytes memory executeOnBehalf = new YulHelper().getCode("ExecuteOnBehalf.sol/ExecuteOnBehalf.json"); address executeOnBehalfAddress = codeJar.saveCode(executeOnBehalf); vm.startPrank(aliceAccount); @@ -84,10 +84,10 @@ contract ExecutorTest is Test { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); + bytes memory ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); address ethcallAddress = codeJar.saveCode(ethcall); - bytes memory executeOnBehalf = new YulHelper().getDeployed("ExecuteOnBehalf.sol/ExecuteOnBehalf.json"); + bytes memory executeOnBehalf = new YulHelper().getCode("ExecuteOnBehalf.sol/ExecuteOnBehalf.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, diff --git a/test/quark-core/QuarkStateManager.t.sol b/test/quark-core/QuarkStateManager.t.sol index 64109c71..30871c7c 100644 --- a/test/quark-core/QuarkStateManager.t.sol +++ b/test/quark-core/QuarkStateManager.t.sol @@ -56,7 +56,7 @@ contract QuarkStateManagerTest is Test { // a QuarkWallet can use nonce 0 as the active nonce vm.pauseGasMetering(); // do not meter deployment gas QuarkWallet wallet = new QuarkWalletStandalone(address(0), address(0), codeJar, stateManager); - bytes memory getMessageDetails = new YulHelper().getDeployed("GetMessageDetails.sol/GetMessageDetails.json"); + bytes memory getMessageDetails = new YulHelper().getCode("GetMessageDetails.sol/GetMessageDetails.json"); address scriptAddress = codeJar.saveCode(getMessageDetails); bytes memory call = abi.encodeWithSignature("getMsgSenderAndValue()"); vm.resumeGasMetering(); @@ -108,7 +108,7 @@ contract QuarkStateManagerTest is Test { Counter counter = new Counter(); assertEq(counter.number(), 0); - bytes memory maxCounterScript = new YulHelper().getDeployed("MaxCounterScript.sol/MaxCounterScript.json"); + bytes memory maxCounterScript = new YulHelper().getCode("MaxCounterScript.sol/MaxCounterScript.json"); address maxCounterScriptAddress = codeJar.saveCode(maxCounterScript); bytes memory call = abi.encodeWithSignature("run(address)", address(counter)); diff --git a/test/quark-core/QuarkWallet.t.sol b/test/quark-core/QuarkWallet.t.sol index f80d2a31..8bfee09f 100644 --- a/test/quark-core/QuarkWallet.t.sol +++ b/test/quark-core/QuarkWallet.t.sol @@ -20,6 +20,7 @@ import {Ethcall} from "quark-core-scripts/src/Ethcall.sol"; import {Logger} from "test/lib/Logger.sol"; import {Counter} from "test/lib/Counter.sol"; import {Reverts} from "test/lib/Reverts.sol"; +import {EmptyCode} from "test/lib/EmptyCode.sol"; import {Incrementer} from "test/lib/Incrementer.sol"; import {PrecompileCaller} from "test/lib/PrecompileCaller.sol"; import {MaxCounterScript} from "test/lib/MaxCounterScript.sol"; @@ -94,7 +95,7 @@ contract QuarkWalletTest is Test { function testSetsMsgSender() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory getMessageDetails = new YulHelper().getDeployed("GetMessageDetails.sol/GetMessageDetails.json"); + bytes memory getMessageDetails = new YulHelper().getCode("GetMessageDetails.sol/GetMessageDetails.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, getMessageDetails, abi.encodeWithSignature("getMsgSenderAndValue()"), ScriptType.ScriptSource ); @@ -113,7 +114,7 @@ contract QuarkWalletTest is Test { // gas: do not meter set-up vm.pauseGasMetering(); QuarkWallet aliceWalletExecutable = newWallet(aliceAccount, aliceAccount); - bytes memory getMessageDetails = new YulHelper().getDeployed("GetMessageDetails.sol/GetMessageDetails.json"); + bytes memory getMessageDetails = new YulHelper().getCode("GetMessageDetails.sol/GetMessageDetails.json"); uint96 nonce = stateManager.nextNonce(address(aliceWalletExecutable)); address scriptAddress = codeJar.saveCode(getMessageDetails); bytes memory call = abi.encodeWithSignature("getMsgSenderAndValue()"); @@ -136,7 +137,7 @@ contract QuarkWalletTest is Test { function testEmitsEventsInExecuteQuarkOperation() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory getMessageDetails = new YulHelper().getDeployed("GetMessageDetails.sol/GetMessageDetails.json"); + bytes memory getMessageDetails = new YulHelper().getCode("GetMessageDetails.sol/GetMessageDetails.json"); QuarkWallet.QuarkOperation memory opWithScriptAddress = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, getMessageDetails, abi.encodeWithSignature("getMsgSenderAndValue()"), ScriptType.ScriptAddress ); @@ -165,7 +166,7 @@ contract QuarkWalletTest is Test { // gas: do not meter set-up vm.pauseGasMetering(); QuarkWallet aliceWalletExecutable = newWallet(aliceAccount, aliceAccount); - bytes memory getMessageDetails = new YulHelper().getDeployed("GetMessageDetails.sol/GetMessageDetails.json"); + bytes memory getMessageDetails = new YulHelper().getCode("GetMessageDetails.sol/GetMessageDetails.json"); uint96 nonce = stateManager.nextNonce(address(aliceWalletExecutable)); address scriptAddress = codeJar.saveCode(getMessageDetails); bytes memory call = abi.encodeWithSignature("getMsgSenderAndValue()"); @@ -181,14 +182,14 @@ contract QuarkWalletTest is Test { /* ===== general invariant tests ===== */ - function testDisallowAllNullNoopScript() public { + function testDisallowAllNullScriptAddress() public { // gas: do not meter set-up vm.pauseGasMetering(); QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ nonce: stateManager.nextNonce(address(aliceWallet)), scriptAddress: address(0), - scriptSource: bytes(""), + scriptSources: new bytes[](0), scriptCalldata: bytes(""), expiry: block.timestamp + 1000 }); @@ -201,21 +202,53 @@ contract QuarkWalletTest is Test { vm.expectRevert(abi.encodeWithSelector(QuarkWallet.EmptyCode.selector)); aliceWallet.executeQuarkOperation(op, v, r, s); - // direct execution of the null script with no calldata will revert + // direct execution of the null script will revert uint96 nonce = stateManager.nextNonce(address(aliceWallet)); vm.prank(HasSignerExecutor(address(aliceWallet)).executor()); vm.expectRevert(abi.encodeWithSelector(QuarkWallet.EmptyCode.selector)); aliceWallet.executeScript(nonce, address(0), bytes("")); + + // gas: do not meter set-up + vm.pauseGasMetering(); + + // NOTE: we cannot deploy the empty code with codeJar, it will revert. + address emptyCodeAddress = address(new EmptyCode()); + + // operation containing a valid empty script will revert + QuarkWallet.QuarkOperation memory op2 = QuarkWallet.QuarkOperation({ + nonce: stateManager.nextNonce(address(aliceWallet)), + scriptAddress: emptyCodeAddress, + scriptSources: new bytes[](0), + scriptCalldata: bytes(""), + expiry: block.timestamp + 1000 + }); + (uint8 v2, bytes32 r2, bytes32 s2) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op2); + + // gas: meter execute + vm.resumeGasMetering(); + + // operation on empty script will revert + vm.expectRevert(abi.encodeWithSelector(QuarkWallet.EmptyCode.selector)); + aliceWallet.executeQuarkOperation(op2, v2, r2, s2); + + // direct execution of empty script will revert + uint96 nonce2 = stateManager.nextNonce(address(aliceWallet)); + vm.prank(HasSignerExecutor(address(aliceWallet)).executor()); + vm.expectRevert(abi.encodeWithSelector(QuarkWallet.EmptyCode.selector)); + aliceWallet.executeScript(nonce2, emptyCodeAddress, bytes("")); } - function testRevertsForOperationWithAddressAndSource() public { + function testRevertsForRandomEmptyScriptAddress() public { // gas: do not meter set-up vm.pauseGasMetering(); + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = new YulHelper().stub(hex"f00f00"); + QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ nonce: stateManager.nextNonce(address(aliceWallet)), scriptAddress: address(0xc0c0), - scriptSource: bytes("f00f00"), + scriptSources: scriptSources, scriptCalldata: bytes("feefee"), expiry: block.timestamp + 1000 }); @@ -224,7 +257,7 @@ contract QuarkWalletTest is Test { // gas: meter execute vm.resumeGasMetering(); - vm.expectRevert(abi.encodeWithSelector(QuarkWallet.AmbiguousScript.selector)); + vm.expectRevert(abi.encodeWithSelector(QuarkWallet.EmptyCode.selector)); aliceWallet.executeQuarkOperation(op, v, r, s); } @@ -233,7 +266,7 @@ contract QuarkWalletTest is Test { function testCanReplaySameScriptWithDifferentCall() public { // gas: disable gas metering except while executing operations vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); // 1. use nonce to increment a counter QuarkWallet.QuarkOperation memory op1 = new QuarkOperationHelper().newBasicOpWithCalldata( @@ -244,10 +277,12 @@ contract QuarkWalletTest is Test { ); (uint8 v1, bytes32 r1, bytes32 s1) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op1); + address incrementerAddress = codeJar.saveCode(incrementer); + QuarkWallet.QuarkOperation memory op2 = QuarkWallet.QuarkOperation({ nonce: op1.nonce, - scriptAddress: address(0), - scriptSource: incrementer, + scriptAddress: incrementerAddress, + scriptSources: new bytes[](0), scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", address(counter)), expiry: block.timestamp + 1000 }); @@ -272,7 +307,7 @@ contract QuarkWalletTest is Test { function testRevertsForReusedNonceWithChangedScript() public { // gas: disable gas metering except while executing operations vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); // 1. use nonce to increment a counter QuarkWallet.QuarkOperation memory op1 = new QuarkOperationHelper().newBasicOpWithCalldata( @@ -286,7 +321,7 @@ contract QuarkWalletTest is Test { QuarkWallet.QuarkOperation memory op2 = QuarkWallet.QuarkOperation({ nonce: op1.nonce, scriptAddress: address(counter), - scriptSource: bytes(""), + scriptSources: new bytes[](0), scriptCalldata: bytes(""), expiry: op1.expiry }); @@ -307,8 +342,8 @@ contract QuarkWalletTest is Test { function testRevertsForReplayOfCanceledScript() public { // gas: disable gas metering except while executing operations vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); - bytes memory cancelOtherScript = new YulHelper().getDeployed("CancelOtherScript.sol/CancelOtherScript.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); + bytes memory cancelOtherScript = new YulHelper().getCode("CancelOtherScript.sol/CancelOtherScript.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, @@ -349,7 +384,7 @@ contract QuarkWalletTest is Test { // gas: disable metering except while executing operations vm.pauseGasMetering(); QuarkWallet aliceWalletExecutable = newWallet(aliceAccount, aliceAccount); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); address incrementerAddress = codeJar.saveCode(incrementer); uint96 nonce = stateManager.nextNonce(address(aliceWalletExecutable)); bytes memory call = abi.encodeWithSignature("incrementCounter(address)", counter); @@ -372,8 +407,8 @@ contract QuarkWalletTest is Test { // gas: disable metering except while executing operations vm.pauseGasMetering(); QuarkWallet aliceWalletExecutable = newWallet(aliceAccount, address(aliceWallet)); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); - bytes memory ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); + bytes memory ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); address incrementerAddress = codeJar.saveCode(incrementer); bytes memory ethcallCalldata = abi.encodeWithSelector( Ethcall.run.selector, @@ -406,7 +441,7 @@ contract QuarkWalletTest is Test { function testRevertsForDirectExecuteByNonExecutorSigner() public { // gas: disable metering except while executing operations vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); assertEq(counter.number(), 0); // act as the signer for the wallet @@ -431,7 +466,7 @@ contract QuarkWalletTest is Test { function testRevertsForUnauthorizedDirectExecuteByRandomAddress() public { // gas: disable metering except while executing operations vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); assertEq(counter.number(), 0); // pre-compute execution parameters so that the revert is expected from the right call @@ -453,19 +488,19 @@ contract QuarkWalletTest is Test { assertEq(counter.number(), 0); } - /* ===== basic operation tests, all run via both ScriptTypes ===== */ + /* ===== basic operation tests ===== */ - function _testAtomicMaxCounter(ScriptType scriptType) internal { + function testAtomicMaxCounterScript() public { // gas: disable metering except while executing operations vm.pauseGasMetering(); - bytes memory maxCounterScript = new YulHelper().getDeployed("MaxCounterScript.sol/MaxCounterScript.json"); + bytes memory maxCounterScript = new YulHelper().getCode("MaxCounterScript.sol/MaxCounterScript.json"); assertEq(counter.number(), 0); vm.startPrank(address(aliceAccount)); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - aliceWallet, maxCounterScript, abi.encodeCall(MaxCounterScript.run, (counter)), scriptType + aliceWallet, maxCounterScript, abi.encodeCall(MaxCounterScript.run, (counter)), ScriptType.ScriptAddress ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); @@ -520,88 +555,129 @@ contract QuarkWalletTest is Test { vm.stopPrank(); } - function testAtomicMaxCounterScriptWithScriptSource() public { - _testAtomicMaxCounter(ScriptType.ScriptSource); - } - - function testAtomicMaxCounterScriptWithScriptAddress() public { - _testAtomicMaxCounter(ScriptType.ScriptAddress); - } - - function _testEmptyScriptRevert(ScriptType scriptType) internal { + function testQuarkOperationRevertsIfCallReverts() public { // gas: do not meter set-up vm.pauseGasMetering(); - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - aliceWallet, abi.encode(), abi.encodeWithSignature("x()"), scriptType - ); + bytes memory revertsCode = new YulHelper().getCode("Reverts.sol/Reverts.json"); + QuarkWallet.QuarkOperation memory op = + new QuarkOperationHelper().newBasicOp(aliceWallet, revertsCode, ScriptType.ScriptSource); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); // gas: meter execute vm.resumeGasMetering(); - vm.expectRevert(abi.encodeWithSelector(QuarkWallet.EmptyCode.selector)); + vm.expectRevert(abi.encodeWithSelector(Reverts.Whoops.selector)); aliceWallet.executeQuarkOperation(op, v, r, s); } - function testEmptyScriptRevertForScriptSource() public { - _testEmptyScriptRevert(ScriptType.ScriptSource); - } - - function testEmptyScriptRevertForScriptAddress() public { - _testEmptyScriptRevert(ScriptType.ScriptAddress); - } - - function _testQuarkOperationRevertsIfCallReverts(ScriptType scriptType) internal { + function testAtomicPing() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory revertsCode = new YulHelper().getDeployed("Reverts.sol/Reverts.json"); + bytes memory ping = new YulHelper().getCode("Logger.sol/Logger.json"); QuarkWallet.QuarkOperation memory op = - new QuarkOperationHelper().newBasicOp(aliceWallet, revertsCode, scriptType); + new QuarkOperationHelper().newBasicOp(aliceWallet, ping, ScriptType.ScriptAddress); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); // gas: meter execute vm.resumeGasMetering(); - vm.expectRevert(abi.encodeWithSelector(Reverts.Whoops.selector)); + // TODO: Check who emitted. + vm.expectEmit(false, false, false, true); + emit Ping(55); aliceWallet.executeQuarkOperation(op, v, r, s); } - function testQuarkOperationWithScriptSourceRevertsIfCallReverts() public { - _testQuarkOperationRevertsIfCallReverts(ScriptType.ScriptSource); - } - - function testQuarkOperationWithScriptAddressRevertsIfCallReverts() public { - _testQuarkOperationRevertsIfCallReverts(ScriptType.ScriptAddress); - } - - function _testAtomicPing(ScriptType scriptType) internal { + function testAtomicPingWithExternalSignature() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory ping = new YulHelper().getDeployed("Logger.sol/Logger.json"); - QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOp(aliceWallet, ping, scriptType); - (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); + assertEq(address(codeJar), address(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f)); + + /* + Run `cat test/lib/Ping.yul0 | solc --bin --yul --evm-version paris -` + */ + + uint96 nonce = aliceWallet.stateManager().nextNonce(address(aliceWallet)); + bytes memory pingCode = + hex"6000356000527f48257dc961b6f792c2b78a080dacfed693b660960a702de21cee364e20270e2f60206000a1600080f3"; + bytes memory pingInitCode = new YulHelper().stub(pingCode); + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = pingInitCode; + address ping = codeJar.getCodeAddress(pingInitCode); + address scriptAddress = ping; + bytes memory scriptCalldata = hex"00000000000000000000000000000000000000000000000000000000000000dd"; + uint256 expiry = 9999999999999; + + // Note: these valueas are test so that if the test case changes any extrinsic values, we have + // a log of those values and can change the test accordingly. + + assertEq(aliceWallet.NAME(), "Quark Wallet"); + assertEq(aliceWallet.VERSION(), "1"); + assertEq(block.chainid, 31337); + assertEq(address(aliceWallet), address(0xc7183455a4C133Ae270771860664b6B7ec320bB1)); + + assertEq(nonce, 0); // nonce + assertEq(scriptAddress, address(0x4a925cF75dcc5708671004d9bbFAf4DCF2C762B0)); // scriptAddress + assertEq(scriptSources.length, 1); // scriptSources + assertEq( + scriptSources[0], + hex"630000003080600e6000396000f36000356000527f48257dc961b6f792c2b78a080dacfed693b660960a702de21cee364e20270e2f60206000a1600080f3" + ); // scriptSources + assertEq(scriptCalldata, hex"00000000000000000000000000000000000000000000000000000000000000dd"); + assertEq(expiry, 9999999999999); + + QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ + scriptAddress: scriptAddress, + scriptSources: scriptSources, + scriptCalldata: scriptCalldata, + nonce: nonce, + expiry: expiry + }); + + /* + ethers.TypedDataEncoder.encode( + { + name: 'Quark Wallet', + version: '1', + chainId: 31337, + verifyingContract: '0xc7183455a4C133Ae270771860664b6B7ec320bB1' + }, + { QuarkOperation: [ + { name: 'nonce', type: 'uint96' }, + { name: 'scriptAddress', type: 'address' }, + { name: 'scriptSources', type: 'bytes[]' }, + { name: 'scriptCalldata', type: 'bytes' }, + { name: 'expiry', type: 'uint256' } + ]}, + { + nonce: 0, + scriptAddress: '0x4a925cF75dcc5708671004d9bbFAf4DCF2C762B0', + scriptSources: ['0x630000003080600e6000396000f36000356000527f48257dc961b6f792c2b78a080dacfed693b660960a702de21cee364e20270e2f60206000a1600080f3'], + scriptCalldata: '0x00000000000000000000000000000000000000000000000000000000000000dd', + expiry: 9999999999999 + } + ) + */ + + bytes memory sigHash = + hex"1901420cb4769bd47ac11897b8b69b8d80a84b9ec8b69437cd42529681d583a6b5216eda58953a1afd7dbc4ddbbef80dbca893fb0a87251b79a6b856708f619d9fcc"; + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePrivateKey, keccak256(sigHash)); // gas: meter execute vm.resumeGasMetering(); // TODO: Check who emitted. - vm.expectEmit(false, false, false, true); - emit Ping(55); + vm.expectEmit(true, true, true, true); + emit Ping(0xdd); aliceWallet.executeQuarkOperation(op, v, r, s); } - function testAtomicPingWithScriptSource() public { - _testAtomicPing(ScriptType.ScriptSource); - } - - function testAtomicPingWithScriptAddress() public { - _testAtomicPing(ScriptType.ScriptAddress); - } - - function _testAtomicIncrementer(ScriptType scriptType) internal { + function testAtomicIncrementer() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); assertEq(counter.number(), 0); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( - aliceWallet, incrementer, abi.encodeWithSignature("incrementCounter(address)", counter), scriptType + aliceWallet, + incrementer, + abi.encodeWithSignature("incrementCounter(address)", counter), + ScriptType.ScriptAddress ); (uint8 v, bytes32 r, bytes32 s) = new SignatureHelper().signOp(alicePrivateKey, aliceWallet, op); @@ -611,20 +687,12 @@ contract QuarkWalletTest is Test { assertEq(counter.number(), 3); } - function testAtomicIncrementerWithScriptSource() public { - _testAtomicIncrementer(ScriptType.ScriptSource); - } - - function testAtomicIncrementerWithScriptAddress() public { - _testAtomicIncrementer(ScriptType.ScriptAddress); - } - /* ===== execution on Precompiles ===== */ // Quark is no longer able call precompiles directly due to empty code check, so these tests are commented out function testPrecompileEcRecover() public { vm.pauseGasMetering(); - bytes memory preCompileCaller = new YulHelper().getDeployed("PrecompileCaller.sol/PrecompileCaller.json"); + bytes memory preCompileCaller = new YulHelper().getCode("PrecompileCaller.sol/PrecompileCaller.json"); bytes32 testHash = keccak256("test"); (uint8 vt, bytes32 rt, bytes32 st) = vm.sign(alicePrivateKey, testHash); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( @@ -646,7 +714,7 @@ contract QuarkWalletTest is Test { // (uint8 vt, bytes32 rt, bytes32 st) = vm.sign(alicePrivateKey, testHash); // QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ // scriptAddress: address(0x1), - // scriptSource: "", + // scriptSources: new bytes[](0), // scriptCalldata: abi.encode(testHash, vt, rt, st), // nonce: aliceWallet.stateManager().nextNonce(address(aliceWallet)), // expiry: block.timestamp + 1000 @@ -660,7 +728,7 @@ contract QuarkWalletTest is Test { function testPrecompileSha256() public { vm.pauseGasMetering(); - bytes memory preCompileCaller = new YulHelper().getDeployed("PrecompileCaller.sol/PrecompileCaller.json"); + bytes memory preCompileCaller = new YulHelper().getCode("PrecompileCaller.sol/PrecompileCaller.json"); uint256 numberToHash = 123; QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, @@ -680,7 +748,7 @@ contract QuarkWalletTest is Test { // uint256 numberToHash = 123; // QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ // scriptAddress: address(0x2), - // scriptSource: "", + // scriptSources: new bytes[](0), // scriptCalldata: abi.encode(numberToHash), // nonce: aliceWallet.stateManager().nextNonce(address(aliceWallet)), // expiry: block.timestamp + 1000 @@ -694,7 +762,7 @@ contract QuarkWalletTest is Test { function testPrecompileRipemd160() public { vm.pauseGasMetering(); - bytes memory preCompileCaller = new YulHelper().getDeployed("PrecompileCaller.sol/PrecompileCaller.json"); + bytes memory preCompileCaller = new YulHelper().getCode("PrecompileCaller.sol/PrecompileCaller.json"); bytes memory testBytes = abi.encodePacked(keccak256("test")); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, @@ -713,7 +781,7 @@ contract QuarkWalletTest is Test { // bytes memory testBytes = abi.encodePacked(keccak256("test")); // QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ // scriptAddress: address(0x3), - // scriptSource: "", + // scriptSources: new bytes[](0), // scriptCalldata: testBytes, // nonce: aliceWallet.stateManager().nextNonce(address(aliceWallet)), // expiry: block.timestamp + 1000 @@ -726,7 +794,7 @@ contract QuarkWalletTest is Test { function testPrecompileDataCopy() public { vm.pauseGasMetering(); - bytes memory preCompileCaller = new YulHelper().getDeployed("PrecompileCaller.sol/PrecompileCaller.json"); + bytes memory preCompileCaller = new YulHelper().getCode("PrecompileCaller.sol/PrecompileCaller.json"); bytes memory testBytes = abi.encodePacked(keccak256("testDataCopy")); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, @@ -745,7 +813,7 @@ contract QuarkWalletTest is Test { // bytes memory testBytes = abi.encodePacked(keccak256("testDataCopy")); // QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ // scriptAddress: address(0x4), - // scriptSource: "", + // scriptSources: new bytes[](0), // scriptCalldata: testBytes, // nonce: aliceWallet.stateManager().nextNonce(address(aliceWallet)), // expiry: block.timestamp + 1000 @@ -758,7 +826,7 @@ contract QuarkWalletTest is Test { function testPrecompileBigModExp() public { vm.pauseGasMetering(); - bytes memory preCompileCaller = new YulHelper().getDeployed("PrecompileCaller.sol/PrecompileCaller.json"); + bytes memory preCompileCaller = new YulHelper().getCode("PrecompileCaller.sol/PrecompileCaller.json"); bytes32 base = bytes32(uint256(7)); bytes32 exponent = bytes32(uint256(3)); bytes32 modulus = bytes32(uint256(11)); @@ -785,7 +853,7 @@ contract QuarkWalletTest is Test { // bytes32 expected = bytes32(uint256(2)); // QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ // scriptAddress: address(0x5), - // scriptSource: "", + // scriptSources: new bytes[](0), // scriptCalldata: abi.encode(uint256(0x20), uint256(0x20), uint256(0x20), base, exponent, modulus), // nonce: aliceWallet.stateManager().nextNonce(address(aliceWallet)), // expiry: block.timestamp + 1000 @@ -798,7 +866,7 @@ contract QuarkWalletTest is Test { function testPrecompileBn256Add() public { vm.pauseGasMetering(); - bytes memory preCompileCaller = new YulHelper().getDeployed("PrecompileCaller.sol/PrecompileCaller.json"); + bytes memory preCompileCaller = new YulHelper().getCode("PrecompileCaller.sol/PrecompileCaller.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, preCompileCaller, @@ -822,7 +890,7 @@ contract QuarkWalletTest is Test { // input[3] = uint256(2); // QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ // scriptAddress: address(0x6), - // scriptSource: "", + // scriptSources: new bytes[](0), // scriptCalldata: abi.encode(input), // nonce: aliceWallet.stateManager().nextNonce(address(aliceWallet)), // expiry: block.timestamp + 1000 @@ -837,7 +905,7 @@ contract QuarkWalletTest is Test { function testPrecompileBn256ScalarMul() public { vm.pauseGasMetering(); - bytes memory preCompileCaller = new YulHelper().getDeployed("PrecompileCaller.sol/PrecompileCaller.json"); + bytes memory preCompileCaller = new YulHelper().getCode("PrecompileCaller.sol/PrecompileCaller.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, preCompileCaller, @@ -860,7 +928,7 @@ contract QuarkWalletTest is Test { // input[2] = uint256(3); // QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ // scriptAddress: address(0x7), - // scriptSource: "", + // scriptSources: new bytes[](0), // scriptCalldata: abi.encode(input), // nonce: aliceWallet.stateManager().nextNonce(address(aliceWallet)), // expiry: block.timestamp + 1000 @@ -875,7 +943,7 @@ contract QuarkWalletTest is Test { function testPrecompileBlake2F() public { vm.pauseGasMetering(); - bytes memory preCompileCaller = new YulHelper().getDeployed("PrecompileCaller.sol/PrecompileCaller.json"); + bytes memory preCompileCaller = new YulHelper().getCode("PrecompileCaller.sol/PrecompileCaller.json"); uint32 rounds = 12; bytes32[2] memory h; @@ -938,7 +1006,7 @@ contract QuarkWalletTest is Test { // QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ // scriptAddress: address(0x9), - // scriptSource: "", + // scriptSources: new bytes[](0), // scriptCalldata: abi.encodePacked(rounds, h[0], h[1], m[0], m[1], m[2], m[3], t[0], t[1], f), // nonce: aliceWallet.stateManager().nextNonce(address(aliceWallet)), // expiry: block.timestamp + 1000 @@ -971,7 +1039,7 @@ contract QuarkWalletTest is Test { { return QuarkWallet.QuarkOperation({ scriptAddress: preCompileAddress, - scriptSource: "", + scriptSources: new bytes[](0), scriptCalldata: hex"", nonce: nonce, expiry: block.timestamp + 1000 diff --git a/test/quark-core/Reverts.t.sol b/test/quark-core/Reverts.t.sol index f51165c4..f1060705 100644 --- a/test/quark-core/Reverts.t.sol +++ b/test/quark-core/Reverts.t.sol @@ -47,7 +47,7 @@ contract RevertsTest is Test { function testRevertsWhenDividingByZero() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory revertsCode = new YulHelper().getDeployed("Reverts.sol/Reverts.json"); + bytes memory revertsCode = new YulHelper().getCode("Reverts.sol/Reverts.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, revertsCode, abi.encodeWithSelector(Reverts.divideByZero.selector), ScriptType.ScriptAddress ); @@ -63,7 +63,7 @@ contract RevertsTest is Test { function testRevertsInteger() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory revertsCode = new YulHelper().getDeployed("Reverts.sol/Reverts.json"); + bytes memory revertsCode = new YulHelper().getCode("Reverts.sol/Reverts.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, revertsCode, abi.encodeWithSelector(Reverts.revertSeven.selector), ScriptType.ScriptAddress ); @@ -78,7 +78,7 @@ contract RevertsTest is Test { function testRevertsOutOfGas() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory revertsCode = new YulHelper().getDeployed("Reverts.sol/Reverts.json"); + bytes memory revertsCode = new YulHelper().getCode("Reverts.sol/Reverts.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, revertsCode, abi.encodeWithSelector(Reverts.outOfGas.selector), ScriptType.ScriptAddress ); @@ -94,7 +94,7 @@ contract RevertsTest is Test { function testRevertsInvalidOpcode() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory revertsCode = new YulHelper().getDeployed("Reverts.sol/Reverts.json"); + bytes memory revertsCode = new YulHelper().getCode("Reverts.sol/Reverts.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( aliceWallet, revertsCode, diff --git a/test/quark-core/isValidSignature.t.sol b/test/quark-core/isValidSignature.t.sol index 6924fde9..f5fecfd5 100644 --- a/test/quark-core/isValidSignature.t.sol +++ b/test/quark-core/isValidSignature.t.sol @@ -14,6 +14,7 @@ import {SignatureHelper} from "test/lib/SignatureHelper.sol"; import {Logger} from "test/lib/Logger.sol"; import {Counter} from "test/lib/Counter.sol"; +import {EmptyCode} from "test/lib/EmptyCode.sol"; import {Permit2, Permit2Helper} from "test/lib/Permit2Helper.sol"; import {EIP1271Signer, EIP1271Reverter} from "test/lib/EIP1271Signer.sol"; @@ -233,7 +234,7 @@ contract isValidSignatureTest is Test { // gas: do not meter set-up vm.pauseGasMetering(); - address emptyCodeContract = codeJar.saveCode(hex""); + address emptyCodeContract = address(new EmptyCode()); QuarkWallet contractWallet = new QuarkWalletStandalone(emptyCodeContract, address(0), codeJar, stateManager); // signature from bob; doesn't matter because the empty contract will be treated as an EOA and revert ( /* bytes32 digest */ , bytes memory signature) = createTestSignature(bobPrivateKey, bobWallet); @@ -262,7 +263,7 @@ contract isValidSignatureTest is Test { }); Permit2Helper.PermitSingle memory permitSingle = Permit2Helper.PermitSingle({details: permitDetails, spender: bob, sigDeadline: block.timestamp + 100}); - (bytes32 digest, bytes memory signature) = createPermit2Signature(alicePrivateKey, permitSingle); + ( /* bytes32 digest */ , bytes memory signature) = createPermit2Signature(alicePrivateKey, permitSingle); // gas: meter execute vm.resumeGasMetering(); diff --git a/test/quark-core/periphery/BatchExecutor.t.sol b/test/quark-core/periphery/BatchExecutor.t.sol index 208b361f..c68b0d97 100644 --- a/test/quark-core/periphery/BatchExecutor.t.sol +++ b/test/quark-core/periphery/BatchExecutor.t.sol @@ -55,11 +55,11 @@ contract BatchExecutorTest is Test { } function testBatchExecute() public { - // We test multiple operations with different wallets, covering both `scriptAddress` and `scriptSource` use-cases + // We test multiple operations with different wallets // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory ping = new YulHelper().getDeployed("Logger.sol/Logger.json"); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory ping = new YulHelper().getCode("Logger.sol/Logger.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); QuarkWallet.QuarkOperation memory aliceOp = new QuarkOperationHelper().newBasicOp(aliceWallet, ping, ScriptType.ScriptAddress); @@ -105,8 +105,8 @@ contract BatchExecutorTest is Test { function testBatchExecuteDoesNotRevertIfAnyCallsRevert() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory ping = new YulHelper().getDeployed("Logger.sol/Logger.json"); - bytes memory reverts = new YulHelper().getDeployed("Reverts.sol/Reverts.json"); + bytes memory ping = new YulHelper().getCode("Logger.sol/Logger.json"); + bytes memory reverts = new YulHelper().getCode("Reverts.sol/Reverts.json"); QuarkWallet.QuarkOperation memory aliceOp = new QuarkOperationHelper().newBasicOp(aliceWallet, ping, ScriptType.ScriptAddress); diff --git a/test/quark-proxy/QuarkMinimalProxy.t.sol b/test/quark-proxy/QuarkMinimalProxy.t.sol index e4111720..fe9cfc28 100644 --- a/test/quark-proxy/QuarkMinimalProxy.t.sol +++ b/test/quark-proxy/QuarkMinimalProxy.t.sol @@ -52,7 +52,7 @@ contract QuarkMinimalProxyTest is Test { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory testScript = new YulHelper().getDeployed("QuarkMinimalProxy.t.sol/TestHarness.json"); + bytes memory testScript = new YulHelper().getCode("QuarkMinimalProxy.t.sol/TestHarness.json"); QuarkWallet.QuarkOperation memory op = new QuarkOperationHelper().newBasicOpWithCalldata( QuarkWallet(payable(aliceWalletProxy)), testScript, diff --git a/test/quark-proxy/QuarkWalletProxyFactory.t.sol b/test/quark-proxy/QuarkWalletProxyFactory.t.sol index 70c1bb69..a94c34df 100644 --- a/test/quark-proxy/QuarkWalletProxyFactory.t.sol +++ b/test/quark-proxy/QuarkWalletProxyFactory.t.sol @@ -13,8 +13,12 @@ import {QuarkWalletProxyFactory} from "quark-proxy/src/QuarkWalletProxyFactory.s import {Counter} from "test/lib/Counter.sol"; +import {Ethcall} from "quark-core-scripts/src/Ethcall.sol"; import {YulHelper} from "test/lib/YulHelper.sol"; +import {Incrementer} from "test/lib/Incrementer.sol"; +import {ExecuteOnBehalf} from "test/lib/ExecuteOnBehalf.sol"; import {SignatureHelper} from "test/lib/SignatureHelper.sol"; +import {GetMessageDetails} from "test/lib/GetMessageDetails.sol"; contract QuarkWalletProxyFactoryTest is Test { event WalletDeploy(address indexed account, address indexed executor, address walletAddress, bytes32 salt); @@ -101,13 +105,17 @@ contract QuarkWalletProxyFactoryTest is Test { function testCreateAndExecuteCreatesWallet() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); Counter counter = new Counter(); + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = incrementer; + + address incrementerAddress = codeJar.getCodeAddress(incrementer); uint96 nonce = stateManager.nextNonce(factory.walletAddressFor(alice, address(0))); QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ - scriptAddress: address(0), - scriptSource: incrementer, + scriptAddress: incrementerAddress, + scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, expiry: block.timestamp + 1000 @@ -138,13 +146,17 @@ contract QuarkWalletProxyFactoryTest is Test { function testCreateAndExecuteWithSalt() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); Counter counter = new Counter(); + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = incrementer; + + address incrementerAddress = codeJar.getCodeAddress(incrementer); uint96 nonce = stateManager.nextNonce(factory.walletAddressFor(alice, address(0))); QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ - scriptAddress: address(0), - scriptSource: incrementer, + scriptAddress: incrementerAddress, + scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, expiry: block.timestamp + 1000 @@ -177,13 +189,17 @@ contract QuarkWalletProxyFactoryTest is Test { function testExecuteOnExistingWallet() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory incrementer = new YulHelper().getDeployed("Incrementer.sol/Incrementer.json"); + bytes memory incrementer = new YulHelper().getCode("Incrementer.sol/Incrementer.json"); Counter counter = new Counter(); + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = incrementer; + uint96 nonce = stateManager.nextNonce(factory.walletAddressFor(alice, address(0))); + address incrementerAddress = codeJar.getCodeAddress(incrementer); QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ - scriptAddress: address(0), - scriptSource: incrementer, + scriptAddress: incrementerAddress, + scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("incrementCounter(address)", counter), nonce: nonce, expiry: block.timestamp + 1000 @@ -218,12 +234,18 @@ contract QuarkWalletProxyFactoryTest is Test { function testCreateAndExecuteSetsMsgSender() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory getMessageDetails = new YulHelper().getDeployed("GetMessageDetails.sol/GetMessageDetails.json"); + bytes memory getMessageDetails = new YulHelper().getCode("GetMessageDetails.sol/GetMessageDetails.json"); address aliceWallet = factory.walletAddressFor(alice, address(0)); uint96 nonce = stateManager.nextNonce(aliceWallet); + + address getMessageDetailsAddress = codeJar.getCodeAddress(getMessageDetails); + + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = getMessageDetails; + QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ - scriptAddress: address(0), - scriptSource: getMessageDetails, + scriptAddress: getMessageDetailsAddress, + scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature("getMsgSenderAndValue()"), nonce: nonce, expiry: block.timestamp + 1000 @@ -250,13 +272,15 @@ contract QuarkWalletProxyFactoryTest is Test { function testCreateAndExecuteWithSaltSetsMsgSender() public { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory getMessageDetails = new YulHelper().getDeployed("GetMessageDetails.sol/GetMessageDetails.json"); + bytes memory getMessageDetails = new YulHelper().getCode("GetMessageDetails.sol/GetMessageDetails.json"); bytes32 salt = bytes32("salty salt salt"); address aliceWallet = factory.walletAddressForSalt(alice, address(0), salt); uint96 nonce = stateManager.nextNonce(aliceWallet); + address getMessageDetailsAddress = codeJar.getCodeAddress(getMessageDetails); + QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ - scriptAddress: address(0), - scriptSource: getMessageDetails, + scriptAddress: getMessageDetailsAddress, + scriptSources: new bytes[](0), scriptCalldata: abi.encodeWithSignature("getMsgSenderAndValue()"), nonce: nonce, expiry: block.timestamp + 1000 @@ -266,7 +290,22 @@ contract QuarkWalletProxyFactoryTest is Test { // gas: meter execute vm.resumeGasMetering(); - // operation is executed + // we didn't include the script source in scriptSources and we never deployed it! + vm.expectRevert(QuarkWallet.EmptyCode.selector); + factory.createAndExecute(alice, address(0), salt, op, v, r, s); + + // gas: do not meter set-up + vm.pauseGasMetering(); + + // but if we do add it... + op.scriptSources = new bytes[](1); + op.scriptSources[0] = getMessageDetails; + (v, r, s) = new SignatureHelper().signOpForAddress(alicePrivateKey, aliceWallet, op); + + // gas: meter execute + vm.resumeGasMetering(); + + // then the script gets deployed and the operation is executed vm.expectEmit(true, true, true, true); // it creates a wallet emit WalletDeploy(alice, address(0), aliceWallet, salt); @@ -291,10 +330,10 @@ contract QuarkWalletProxyFactoryTest is Test { // gas: do not meter set-up vm.pauseGasMetering(); - bytes memory ethcall = new YulHelper().getDeployed("Ethcall.sol/Ethcall.json"); + bytes memory ethcall = new YulHelper().getCode("Ethcall.sol/Ethcall.json"); address ethcallAddress = codeJar.saveCode(ethcall); - bytes memory executeOnBehalf = new YulHelper().getDeployed("ExecuteOnBehalf.sol/ExecuteOnBehalf.json"); + bytes memory executeOnBehalf = new YulHelper().getCode("ExecuteOnBehalf.sol/ExecuteOnBehalf.json"); Counter counter = new Counter(); @@ -302,9 +341,15 @@ contract QuarkWalletProxyFactoryTest is Test { QuarkWallet aliceWalletPrimary = QuarkWallet(factory.create(alice, address(0))); QuarkWallet aliceWalletSecondary = QuarkWallet(factory.create(alice, address(aliceWalletPrimary), bytes32("1"))); + address executeOnBehalfAddress = codeJar.getCodeAddress(executeOnBehalf); + + // NOTE: necessary to pass this into scriptSources or it will be EmptyCode()! + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = executeOnBehalf; + QuarkWallet.QuarkOperation memory op = QuarkWallet.QuarkOperation({ - scriptAddress: address(0), - scriptSource: executeOnBehalf, + scriptAddress: executeOnBehalfAddress, + scriptSources: scriptSources, scriptCalldata: abi.encodeWithSignature( "run(address,uint96,address,bytes)", address(aliceWalletSecondary),