diff --git a/.gas-snapshot b/.gas-snapshot index 782c3af..a8c3ace 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,76 +1,78 @@ -AddOwnerAddressTest:testEmitsAddOwner() (gas: 91954) -AddOwnerAddressTest:testIncreasesOwnerIndex() (gas: 90492) -AddOwnerAddressTest:testRevertsIfAlreadyOwner() (gas: 92327) -AddOwnerAddressTest:testRevertsIfCalledByNonOwner() (gas: 11831) -AddOwnerAddressTest:testSetsIsOwner() (gas: 90125) -AddOwnerAddressTest:testSetsOwnerAtIndex() (gas: 99961) -AddOwnerPublicKeyTest:testEmitsAddOwner() (gas: 115024) -AddOwnerPublicKeyTest:testFuzzIsOwnerPublicKey(bytes32,bytes32) (runs: 256, μ: 114454, ~: 114454) -AddOwnerPublicKeyTest:testRevertsIfAlreadyOwner() (gas: 115392) -AddOwnerPublicKeyTest:testRevertsIfCalledByNonOwner() (gas: 11895) -AddOwnerPublicKeyTest:testSetsIsOwner() (gas: 113193) -AddOwnerPublicKeyTest:testSetsOwnerAtIndex() (gas: 130925) -CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForDeployedAccount() (gas: 311701) -CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForUndeployedAccount() (gas: 293976) -CoinbaseSmartWalletFactoryTest:testDeployDeterministicPassValues() (gas: 270581) -CoinbaseSmartWalletFactoryTest:test_CreateAccount_ReturnsPredeterminedAddress_WhenAccountAlreadyExists() (gas: 289811) -CoinbaseSmartWalletFactoryTest:test_RevertsIfLength32ButLargerThanAddress() (gas: 303514) -CoinbaseSmartWalletFactoryTest:test_constructor_revertsIfImplementationIsNotDeployed(address) (runs: 256, μ: 39338, ~: 39348) -CoinbaseSmartWalletFactoryTest:test_constructor_setsImplementation(address) (runs: 256, μ: 455736, ~: 455736) -CoinbaseSmartWalletFactoryTest:test_createAccountDeploysToPredeterminedAddress() (gas: 271825) -CoinbaseSmartWalletFactoryTest:test_createAccountSetsOwnersCorrectly() (gas: 281476) -CoinbaseSmartWalletFactoryTest:test_createAccount_emitsAccountCreatedEvent(uint256) (runs: 256, μ: 273358, ~: 273358) -CoinbaseSmartWalletFactoryTest:test_exitIfAccountIsAlreadyInitialized() (gas: 271307) -CoinbaseSmartWalletFactoryTest:test_implementation_returnsExpectedAddress() (gas: 7698) -CoinbaseSmartWalletFactoryTest:test_initCodeHash() (gas: 7913) -CoinbaseSmartWalletFactoryTest:test_revertsIfNoOwners() (gas: 29256) -ERC1271Test:test_returnsExpectedDomainHashWhenProxy() (gas: 31630) -ERC1271Test:test_static() (gas: 4046108) -MultiOwnableInitializeTest:testRevertsIfLength32ButLargerThanAddress() (gas: 80861) -MultiOwnableInitializeTest:testRevertsIfLength32NotAddress() (gas: 81027) -MultiOwnableInitializeTest:testRevertsIfLengthNot32Or64() (gas: 103534) -RemoveLastOwnerTest:test_emitsRemoveOwner() (gas: 50105) -RemoveLastOwnerTest:test_removesOwner() (gas: 48993) -RemoveLastOwnerTest:test_removesOwnerAtIndex() (gas: 49127) -RemoveLastOwnerTest:test_revert_whenCalledByNonOwner(address) (runs: 256, μ: 19210, ~: 19210) -RemoveLastOwnerTest:test_revert_whenNoOwnerAtIndex() (gas: 48103) -RemoveLastOwnerTest:test_revert_whenWrongOwnerAtIndex() (gas: 34023) -RemoveLastOwnerTest:test_reverts_whenNotLastOwner() (gas: 123054) -RemoveOwnerAtIndexTest:test_emitsRemoveOwner() (gas: 55370) -RemoveOwnerAtIndexTest:test_removesOwner() (gas: 54324) -RemoveOwnerAtIndexTest:test_removesOwnerAtIndex() (gas: 54222) -RemoveOwnerAtIndexTest:test_revert_whenCalledByNonOwner(address) (runs: 256, μ: 19232, ~: 19232) -RemoveOwnerAtIndexTest:test_revert_whenNoOwnerAtIndex() (gas: 33098) -RemoveOwnerAtIndexTest:test_revert_whenWrongOwnerAtIndex() (gas: 36598) -RemoveOwnerAtIndexTest:test_reverts_ifIsLastOwner() (gas: 7632833) -TestCanSkipChainIdValidation:test_approvedSelectorsReturnTrue() (gas: 17685) -TestCanSkipChainIdValidation:test_otherSelectorsReturnFalse() (gas: 12561) -TestExecuteWithoutChainIdValidation:testExecute() (gas: 485146) -TestExecuteWithoutChainIdValidation:testExecuteBatch() (gas: 889868) -TestExecuteWithoutChainIdValidation:testExecuteBatch(uint256) (runs: 256, μ: 4544535, ~: 4457018) -TestExecuteWithoutChainIdValidation:test__codesize() (gas: 61710) -TestExecuteWithoutChainIdValidation:test_revertsWithReservedNonce() (gas: 81700) -TestExecuteWithoutChainIdValidation:test_reverts_whenCallerNotEntryPoint() (gas: 11148) -TestExecuteWithoutChainIdValidation:test_reverts_whenOneCallReverts() (gas: 467006) -TestExecuteWithoutChainIdValidation:test_reverts_whenOneSelectorNotApproved() (gas: 179933) -TestExecuteWithoutChainIdValidation:test_reverts_whenSelectorNotApproved() (gas: 106565) -TestExecuteWithoutChainIdValidation:test_succeeds_whenSelectorAllowed() (gas: 424623) -TestImplementation:testImplementation() (gas: 12677) -TestInitialize:testInitialize() (gas: 21146) -TestInitialize:test_cannotInitImplementation() (gas: 3676943) -TestIsValidSignature:testReturnsInvalidIfPasskeySigButWrongOwnerLength() (gas: 40556) -TestIsValidSignature:testRevertsIfEthereumSignatureButWrongOwnerLength() (gas: 24272) -TestIsValidSignature:testRevertsIfOwnerIsInvalidEthereumAddress() (gas: 22001) -TestIsValidSignature:testSmartWalletSigner() (gas: 3967929) -TestIsValidSignature:testValidateSignatureWithEOASigner() (gas: 25053) -TestIsValidSignature:testValidateSignatureWithEOASignerFailsWithWrongSigner() (gas: 23780) -TestIsValidSignature:testValidateSignatureWithPasskeySigner() (gas: 354806) -TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsBadOwnerIndex() (gas: 35960) -TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsWithWrongBadSignature() (gas: 345393) -TestUpgradeToAndCall:testUpgradeToAndCall() (gas: 25322) -TestValidateUserOp:test_reverts_whenReplayableNonceKeyInvalidForSelector() (gas: 14609) -TestValidateUserOp:test_reverts_whenSelectorInvalidForReplayableNonceKey() (gas: 14469) -TestValidateUserOp:test_reverts_whenUpgradeToImplementationWithNoCode(address) (runs: 256, μ: 23208, ~: 23228) -TestValidateUserOp:test_succeedsWithEOASigner() (gas: 456302) -TestValidateUserOp:test_succeedsWithPasskeySigner() (gas: 711409) -TestValidateUserOp:test_succeeds_whenUpgradeToImplementationWithCode() (gas: 3714211) \ No newline at end of file +AddOwnerAddressTest:testEmitsAddOwner() (gas: 95829) +AddOwnerAddressTest:testIncreasesOwnerIndex() (gas: 93811) +AddOwnerAddressTest:testRevertsIfAlreadyOwner() (gas: 97900) +AddOwnerAddressTest:testRevertsIfCalledByNonOwner() (gas: 13315) +AddOwnerAddressTest:testSetsIsOwner() (gas: 94340) +AddOwnerAddressTest:testSetsOwnerAtIndex() (gas: 97345) +AddOwnerPublicKeyTest:testEmitsAddOwner() (gas: 119224) +AddOwnerPublicKeyTest:testFuzzIsOwnerPublicKey(bytes32,bytes32) (runs: 260, μ: 120409, ~: 120409) +AddOwnerPublicKeyTest:testRevertsIfAlreadyOwner() (gas: 121567) +AddOwnerPublicKeyTest:testRevertsIfCalledByNonOwner() (gas: 13468) +AddOwnerPublicKeyTest:testSetsIsOwner() (gas: 117612) +AddOwnerPublicKeyTest:testSetsOwnerAtIndex() (gas: 120856) +CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForDeployedAccount() (gas: 321780) +CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForUndeployedAccount() (gas: 306564) +CoinbaseSmartWalletFactoryTest:testDeployDeterministicPassValues() (gas: 278647) +CoinbaseSmartWalletFactoryTest:test_CreateAccount_ReturnsPredeterminedAddress_WhenAccountAlreadyExists() (gas: 302620) +CoinbaseSmartWalletFactoryTest:test_RevertsIfLength32ButLargerThanAddress() (gas: 312891) +CoinbaseSmartWalletFactoryTest:test_constructor_revertsIfImplementationIsNotDeployed(address) (runs: 260, μ: 40036, ~: 40046) +CoinbaseSmartWalletFactoryTest:test_constructor_setsImplementation(address) (runs: 260, μ: 548238, ~: 548238) +CoinbaseSmartWalletFactoryTest:test_createAccountDeploysToPredeterminedAddress() (gas: 281841) +CoinbaseSmartWalletFactoryTest:test_createAccountSetsOwnersCorrectly() (gas: 293368) +CoinbaseSmartWalletFactoryTest:test_createAccount_emitsAccountCreatedEvent(uint256) (runs: 260, μ: 284216, ~: 284216) +CoinbaseSmartWalletFactoryTest:test_exitIfAccountIsAlreadyInitialized() (gas: 282240) +CoinbaseSmartWalletFactoryTest:test_implementation_returnsExpectedAddress() (gas: 7992) +CoinbaseSmartWalletFactoryTest:test_initCodeHash() (gas: 8307) +CoinbaseSmartWalletFactoryTest:test_revertsIfNoOwners() (gas: 30019) +ERC1271Test:test_returnsExpectedDomainHashWhenProxy() (gas: 23618) +ERC1271Test:test_static() (gas: 5038825) +EndToEndTest:test_transfer_erc20() (gas: 462176) +EndToEndTest:test_transfer_native() (gas: 355253) +MultiOwnableInitializeTest:testRevertsIfLength32ButLargerThanAddress() (gas: 83336) +MultiOwnableInitializeTest:testRevertsIfLength32NotAddress() (gas: 83255) +MultiOwnableInitializeTest:testRevertsIfLengthNot32Or64() (gas: 105695) +RemoveLastOwnerTest:test_emitsRemoveOwner() (gas: 52621) +RemoveLastOwnerTest:test_removesOwner() (gas: 52383) +RemoveLastOwnerTest:test_removesOwnerAtIndex() (gas: 53221) +RemoveLastOwnerTest:test_revert_whenCalledByNonOwner(address) (runs: 260, μ: 20652, ~: 20652) +RemoveLastOwnerTest:test_revert_whenNoOwnerAtIndex() (gas: 50014) +RemoveLastOwnerTest:test_revert_whenWrongOwnerAtIndex() (gas: 37450) +RemoveLastOwnerTest:test_reverts_whenNotLastOwner() (gas: 127056) +RemoveOwnerAtIndexTest:test_emitsRemoveOwner() (gas: 57775) +RemoveOwnerAtIndexTest:test_removesOwner() (gas: 57349) +RemoveOwnerAtIndexTest:test_removesOwnerAtIndex() (gas: 57914) +RemoveOwnerAtIndexTest:test_revert_whenCalledByNonOwner(address) (runs: 260, μ: 20674, ~: 20674) +RemoveOwnerAtIndexTest:test_revert_whenNoOwnerAtIndex() (gas: 35018) +RemoveOwnerAtIndexTest:test_revert_whenWrongOwnerAtIndex() (gas: 40657) +RemoveOwnerAtIndexTest:test_reverts_ifIsLastOwner() (gas: 8529792) +TestCanSkipChainIdValidation:test_approvedSelectorsReturnTrue() (gas: 20637) +TestCanSkipChainIdValidation:test_otherSelectorsReturnFalse() (gas: 13631) +TestExecuteWithoutChainIdValidation:testExecute() (gas: 587208) +TestExecuteWithoutChainIdValidation:testExecuteBatch() (gas: 1087434) +TestExecuteWithoutChainIdValidation:testExecuteBatch(uint256) (runs: 260, μ: 5622658, ~: 5415134) +TestExecuteWithoutChainIdValidation:test__codesize() (gas: 65702) +TestExecuteWithoutChainIdValidation:test_revertsWithReservedNonce() (gas: 87228) +TestExecuteWithoutChainIdValidation:test_reverts_whenCallerNotEntryPoint() (gas: 11407) +TestExecuteWithoutChainIdValidation:test_reverts_whenOneCallReverts() (gas: 475054) +TestExecuteWithoutChainIdValidation:test_reverts_whenOneSelectorNotApproved() (gas: 183931) +TestExecuteWithoutChainIdValidation:test_reverts_whenSelectorNotApproved() (gas: 108819) +TestExecuteWithoutChainIdValidation:test_succeeds_whenSelectorAllowed() (gas: 440590) +TestImplementation:testImplementation() (gas: 12896) +TestInitialize:testInitialize() (gas: 22939) +TestInitialize:test_cannotInitImplementation() (gas: 4597308) +TestIsValidSignature:testReturnsInvalidIfPasskeySigButWrongOwnerLength() (gas: 50912) +TestIsValidSignature:testRevertsIfEthereumSignatureButWrongOwnerLength() (gas: 29788) +TestIsValidSignature:testRevertsIfOwnerIsInvalidEthereumAddress() (gas: 29384) +TestIsValidSignature:testSmartWalletSigner() (gas: 4950306) +TestIsValidSignature:testValidateSignatureWithEOASigner() (gas: 30653) +TestIsValidSignature:testValidateSignatureWithEOASignerFailsWithWrongSigner() (gas: 27974) +TestIsValidSignature:testValidateSignatureWithPasskeySigner() (gas: 275434) +TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsBadOwnerIndex() (gas: 46521) +TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsWithWrongBadSignature() (gas: 270588) +TestUpgradeToAndCall:testUpgradeToAndCall() (gas: 26537) +TestValidateUserOp:test_reverts_whenReplayableNonceKeyInvalidForSelector() (gas: 17983) +TestValidateUserOp:test_reverts_whenSelectorInvalidForReplayableNonceKey() (gas: 19057) +TestValidateUserOp:test_reverts_whenUpgradeToImplementationWithNoCode(address) (runs: 260, μ: 30315, ~: 30315) +TestValidateUserOp:test_succeedsWithEOASigner() (gas: 687856) +TestValidateUserOp:test_succeedsWithPasskeySigner() (gas: 854583) +TestValidateUserOp:test_succeeds_whenUpgradeToImplementationWithCode() (gas: 4688457) \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index f6e778a..afc65bd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "lib/safe-singleton-deployer-sol"] path = lib/safe-singleton-deployer-sol url = https://github.com/wilsoncusack/safe-singleton-deployer-sol +[submodule "lib/v4-core"] + path = lib/v4-core + url = https://github.com/Uniswap/v4-core diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..0c69790 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,32 @@ +{ + "lib/account-abstraction": { + "rev": "abff2aca61a8f0934e533d0d352978055fddbd96" + }, + "lib/forge-std": { + "tag": { + "name": "v1.10.0", + "rev": "8bbcf6e3f8f62f419e5429a0bd89331c85c37824" + } + }, + "lib/openzeppelin-contracts": { + "rev": "5705e8208bc92cd82c7bcdfeac8dbc7377767d96" + }, + "lib/p256-verifier": { + "rev": "29475ae300ec95d98d5c7cc34c094846f0aa2dcd" + }, + "lib/safe-singleton-deployer-sol": { + "rev": "cf2b89c33fed536c4dd6fef2fb84f39053068868" + }, + "lib/solady": { + "rev": "c4c96607cb3aa3807b14c81ae2015bcba061f8fc" + }, + "lib/v4-core": { + "tag": { + "name": "v4.0.0", + "rev": "e50237c43811bd9b526eff40f26772152a42daba" + } + }, + "lib/webauthn-sol": { + "rev": "619f20ab0f074fef41066ee4ab24849a913263b2" + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 0846d5e..f3a6113 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,7 +8,7 @@ optimizer = true optimizer_runs = 999999 via_ir = true evm_version = "prague" -solc_version = "0.8.23" +solc_version = "0.8.26" [fmt] sort_imports = true diff --git a/lib/forge-std b/lib/forge-std index 1fd874f..8bbcf6e 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1fd874f0efdb711cb6807c4f4a000ed2805dc809 +Subproject commit 8bbcf6e3f8f62f419e5429a0bd89331c85c37824 diff --git a/lib/v4-core b/lib/v4-core new file mode 160000 index 0000000..e50237c --- /dev/null +++ b/lib/v4-core @@ -0,0 +1 @@ +Subproject commit e50237c43811bd9b526eff40f26772152a42daba diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..c60d2e3 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,15 @@ +@ensdomains/=lib/v4-core/node_modules/@ensdomains/ +@openzeppelin/=lib/v4-core/lib/openzeppelin-contracts/ +FreshCryptoLib/=lib/webauthn-sol/lib/FreshCryptoLib/solidity/src/ +account-abstraction/=lib/account-abstraction/contracts/ +ds-test/=lib/v4-core/lib/forge-std/lib/ds-test/src/ +erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ +forge-std/=lib/forge-std/src/ +hardhat/=lib/v4-core/node_modules/hardhat/ +openzeppelin-contracts/=lib/openzeppelin-contracts/ +p256-verifier/=lib/p256-verifier/ +safe-singleton-deployer-sol/=lib/safe-singleton-deployer-sol/ +solady/=lib/solady/src/ +solmate/=lib/v4-core/lib/solmate/ +v4-core/=lib/v4-core/src/ +webauthn-sol/=lib/webauthn-sol/src/ diff --git a/snapshots/EndToEndTest.json b/snapshots/EndToEndTest.json new file mode 100644 index 0000000..28edf88 --- /dev/null +++ b/snapshots/EndToEndTest.json @@ -0,0 +1,8 @@ +{ + "e2e_swap_baseAccount": "388633", + "e2e_swap_eoa": "176712", + "e2e_transfer_erc20_baseAccount": "158011", + "e2e_transfer_erc20_eoa": "50584", + "e2e_transfer_native_baseAccount": "133676", + "e2e_transfer_native_eoa": "17326" +} \ No newline at end of file diff --git a/src/CoinbaseSmartWallet.sol b/src/CoinbaseSmartWallet.sol index a3b02fd..a11eb25 100644 --- a/src/CoinbaseSmartWallet.sol +++ b/src/CoinbaseSmartWallet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.23; +pragma solidity 0.8.26; import {IAccount} from "account-abstraction/interfaces/IAccount.sol"; diff --git a/test/gas/EndToEnd.t.sol b/test/gas/EndToEnd.t.sol new file mode 100644 index 0000000..a3497d2 --- /dev/null +++ b/test/gas/EndToEnd.t.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {MockERC20} from "../../lib/solady/test/utils/mocks/MockERC20.sol"; +import {CoinbaseSmartWallet} from "../../src/CoinbaseSmartWallet.sol"; +import {CoinbaseSmartWalletFactory} from "../../src/CoinbaseSmartWalletFactory.sol"; +import {SmartWalletTestBase} from "../CoinbaseSmartWallet/SmartWalletTestBase.sol"; + +import {Static} from "../CoinbaseSmartWallet/Static.sol"; +import {MockTarget} from "../mocks/MockTarget.sol"; +import {UserOperation} from "account-abstraction/interfaces/UserOperation.sol"; +import {console2} from "forge-std/Test.sol"; + +// Uniswap v4 imports +import {PoolManager} from "v4-core/PoolManager.sol"; +import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; +import {PoolSwapTest} from "v4-core/test/PoolSwapTest.sol"; +import {Currency, CurrencyLibrary} from "v4-core/types/Currency.sol"; +import {PoolKey} from "v4-core/types/PoolKey.sol"; +import {IHooks} from "v4-core/interfaces/IHooks.sol"; +import {BalanceDelta} from "v4-core/types/BalanceDelta.sol"; +import {TickMath} from "v4-core/libraries/TickMath.sol"; +import {PoolModifyLiquidityTest} from "v4-core/test/PoolModifyLiquidityTest.sol"; +import {LiquidityAmounts} from "../../lib/v4-core/test/utils/LiquidityAmounts.sol"; + +/// forge-config: default.isolate = true +contract EndToEndTest is SmartWalletTestBase { + using CurrencyLibrary for Currency; + + address eoaUser = address(0xe0a); + + MockERC20 usdc; + MockERC20 weth; + MockTarget target; + CoinbaseSmartWalletFactory factory; + + // Uniswap v4 contracts + IPoolManager poolManager; + PoolSwapTest swapRouter; + PoolModifyLiquidityTest modifyLiquidityRouter; + PoolKey poolKey; + + uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336; // sqrt(1) * 2^96 + uint160 constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; + uint160 constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1; + + function setUp() public override { + vm.etch(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789, Static.ENTRY_POINT_BYTES); + + CoinbaseSmartWallet implementation = new CoinbaseSmartWallet(); + factory = new CoinbaseSmartWalletFactory(address(implementation)); + + signerPrivateKey = 0xa11ce; + signer = vm.addr(signerPrivateKey); + owners.push(abi.encode(signer)); + account = factory.createAccount(owners, 0); + + vm.deal(address(account), 100 ether); + vm.deal(eoaUser, 100 ether); + + usdc = new MockERC20("USD Coin", "USDC", 6); + weth = new MockERC20("Wrapped Ether", "WETH", 18); + + usdc.mint(address(account), 10000e6); + usdc.mint(eoaUser, 10000e6); + weth.mint(address(account), 100e18); + weth.mint(eoaUser, 100e18); + + // For liquidity provision + usdc.mint(address(this), 1000000e6); + weth.mint(address(this), 1000e18); + + target = new MockTarget(); + + poolManager = new PoolManager(address(this)); + swapRouter = new PoolSwapTest(poolManager); + modifyLiquidityRouter = new PoolModifyLiquidityTest(poolManager); + + setupUniswapV4Pool(); + } + + function test_transfer_native() public { + // Dust to avoid first-time transfer gas cost + vm.deal(address(0x1234), 1 wei); + usdc.mint(eoaUser, 1 wei); + + userOpCalldata = abi.encodeCall(CoinbaseSmartWallet.execute, (address(0x1234), 1 ether, "")); + UserOperation memory op = _getUserOpWithSignature(); + + bytes memory handleOpsCalldata = abi.encodeCall(entryPoint.handleOps, (_makeOpsArray(op), payable(bundler))); + console2.log("test_transfer_native Base Account calldata size:", handleOpsCalldata.length); + + vm.startSnapshotGas("e2e_transfer_native_baseAccount"); + _sendUserOperation(op); + uint256 gas4337 = vm.stopSnapshotGas(); + console2.log("test_transfer_native Base Account gas:", gas4337); + + console2.log("test_transfer_native EOA calldata size:", uint256(0)); + + vm.prank(eoaUser); + vm.startSnapshotGas("e2e_transfer_native_eoa"); + payable(address(0x1234)).transfer(1 ether); + uint256 gasEOA = vm.stopSnapshotGas(); + console2.log("test_transfer_native EOA gas:", gasEOA); + console2.log("Gas overhead (4337 Base Account / EOA):", (gas4337 * 100) / gasEOA, "%"); + } + + function test_transfer_erc20() public { + vm.deal(address(0x5678), 1 wei); + usdc.mint(eoaUser, 1 wei); + + userOpCalldata = abi.encodeCall( + CoinbaseSmartWallet.execute, (address(usdc), 0, abi.encodeCall(usdc.transfer, (address(0x5678), 100e6))) + ); + UserOperation memory op = _getUserOpWithSignature(); + + bytes memory handleOpsCalldata = abi.encodeCall(entryPoint.handleOps, (_makeOpsArray(op), payable(bundler))); + console2.log("test_transfer_erc20 Base Account calldata size:", handleOpsCalldata.length); + + vm.startSnapshotGas("e2e_transfer_erc20_baseAccount"); + _sendUserOperation(op); + uint256 gas4337 = vm.stopSnapshotGas(); + console2.log("test_transfer_erc20 Base Account gas:", gas4337); + + bytes memory eoaCalldata = abi.encodeCall(usdc.transfer, (address(0x5678), 100e6)); + console2.log("test_transfer_erc20 EOA calldata size:", eoaCalldata.length); + + vm.prank(eoaUser); + vm.startSnapshotGas("e2e_transfer_erc20_eoa"); + usdc.transfer(address(0x5678), 100e6); + uint256 gasEOA = vm.stopSnapshotGas(); + console2.log("test_transfer_erc20 EOA gas:", gasEOA); + console2.log("Gas overhead (4337 Base Account / EOA):", (gas4337 * 100) / gasEOA, "%"); + } + + function test_swap() public { + vm.prank(address(account)); + usdc.approve(address(swapRouter), type(uint256).max); + vm.prank(address(account)); + weth.approve(address(swapRouter), type(uint256).max); + + vm.prank(eoaUser); + usdc.approve(address(swapRouter), type(uint256).max); + vm.prank(eoaUser); + weth.approve(address(swapRouter), type(uint256).max); + + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: -1000e6, // Exact input: 1000 USDC + sqrtPriceLimitX96: MIN_PRICE_LIMIT + }); + bytes memory swapCalldata = abi.encodeCall( + PoolSwapTest.swap, + (poolKey, params, PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}), "") + ); + + userOpCalldata = abi.encodeCall( + CoinbaseSmartWallet.execute, + (address(swapRouter), 0, swapCalldata) + ); + UserOperation memory op = _getUserOpWithSignature(); + + bytes memory handleOpsCalldata = abi.encodeCall(entryPoint.handleOps, (_makeOpsArray(op), payable(bundler))); + console2.log("test_swap Base Account calldata size:", handleOpsCalldata.length); + + vm.startSnapshotGas("e2e_swap_baseAccount"); + _sendUserOperation(op); + uint256 gas4337 = vm.stopSnapshotGas(); + console2.log("test_swap Base Account gas:", gas4337); + + console2.log("test_swap EOA calldata size:", swapCalldata.length); + + vm.prank(eoaUser); + vm.startSnapshotGas("e2e_swap_eoa"); + swapRouter.swap(poolKey, params, PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}), ""); + uint256 gasEOA = vm.stopSnapshotGas(); + console2.log("test_swap EOA gas:", gasEOA); + console2.log("Gas overhead (4337 Base Account / EOA):", (gas4337 * 100) / gasEOA, "%"); + } + + function _makeOpsArray(UserOperation memory op) internal pure returns (UserOperation[] memory) { + UserOperation[] memory ops = new UserOperation[](1); + ops[0] = op; + return ops; + } + + function _sign(UserOperation memory userOp) internal view override returns (bytes memory signature) { + bytes32 toSign = entryPoint.getUserOpHash(userOp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, toSign); + signature = abi.encode(CoinbaseSmartWallet.SignatureWrapper(0, abi.encodePacked(r, s, v))); + } + + function setupUniswapV4Pool() internal { + Currency currency0; + Currency currency1; + + if (address(usdc) < address(weth)) { + currency0 = Currency.wrap(address(usdc)); + currency1 = Currency.wrap(address(weth)); + } else { + currency0 = Currency.wrap(address(weth)); + currency1 = Currency.wrap(address(usdc)); + } + + poolKey = PoolKey({ + currency0: currency0, + currency1: currency1, + fee: 3000, + tickSpacing: 60, + hooks: IHooks(address(0)) + }); + + // sqrtPriceX96 = sqrt(10^12) * 2^96 for USDC/WETH decimal adjustment + uint160 sqrtPriceX96 = 79228162514264337593543950336 * 1e6; + poolManager.initialize(poolKey, sqrtPriceX96); + + usdc.approve(address(modifyLiquidityRouter), type(uint256).max); + weth.approve(address(modifyLiquidityRouter), type(uint256).max); + + IPoolManager.ModifyLiquidityParams memory params = IPoolManager.ModifyLiquidityParams({ + tickLower: -887220, + tickUpper: 887220, + liquidityDelta: 1000e6, + salt: 0 + }); + + modifyLiquidityRouter.modifyLiquidity(poolKey, params, ""); + + usdc.approve(address(poolManager), type(uint256).max); + weth.approve(address(poolManager), type(uint256).max); + } +}