Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion foundry.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
"rev": "abff2aca61a8f0934e533d0d352978055fddbd96"
},
"lib/forge-std": {
"rev": "1fd874f0efdb711cb6807c4f4a000ed2805dc809"
"tag": {
"name": "v1.10.0",
"rev": "8bbcf6e3f8f62f419e5429a0bd89331c85c37824"
}
},
"lib/openzeppelin-contracts": {
"rev": "5705e8208bc92cd82c7bcdfeac8dbc7377767d96"
Expand Down
2 changes: 1 addition & 1 deletion lib/forge-std
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it would have been ideal to have put this update in the prior PR which was doing more boilerplate/clean slate stuff, but no need to split this out at this point.

Submodule forge-std updated 55 files
+1 −0 .github/CODEOWNERS
+57 −109 .github/workflows/ci.yml
+3 −1 .github/workflows/sync.yml
+0 −3 .gitmodules
+193 −0 CONTRIBUTING.md
+21 −5 README.md
+5 −3 foundry.toml
+0 −1 lib/ds-test
+1 −1 package.json
+13 −2 scripts/vm.py
+16 −9 src/Base.sol
+1 −0 src/Script.sol
+590 −202 src/StdAssertions.sol
+49 −11 src/StdChains.sol
+17 −5 src/StdCheats.sol
+30 −0 src/StdConstants.sol
+18 −3 src/StdInvariant.sol
+111 −11 src/StdJson.sol
+202 −107 src/StdStorage.sol
+283 −0 src/StdToml.sol
+1 −19 src/StdUtils.sol
+5 −4 src/Test.sol
+1,416 −62 src/Vm.sol
+635 −608 src/console.sol
+1 −1,555 src/console2.sol
+1 −1 src/interfaces/IERC1155.sol
+3 −3 src/interfaces/IERC4626.sol
+72 −0 src/interfaces/IERC6909.sol
+1 −1 src/interfaces/IERC721.sol
+150 −0 src/interfaces/IERC7540.sol
+241 −0 src/interfaces/IERC7575.sol
+0 −216 src/mocks/MockERC20.sol
+0 −221 src/mocks/MockERC721.sol
+693 −4 src/safeconsole.sol
+44 −0 test/CommonBase.t.sol
+35 −909 test/StdAssertions.t.sol
+55 −44 test/StdChains.t.sol
+66 −37 test/StdCheats.t.sol
+38 −0 test/StdConstants.t.sol
+12 −12 test/StdError.t.sol
+49 −0 test/StdJson.t.sol
+12 −22 test/StdMath.t.sol
+190 −17 test/StdStorage.t.sol
+1 −1 test/StdStyle.t.sol
+49 −0 test/StdToml.t.sol
+26 −26 test/StdUtils.t.sol
+9 −6 test/Vm.t.sol
+1 −1 test/compilation/CompilationScript.sol
+1 −1 test/compilation/CompilationScriptBase.sol
+1 −1 test/compilation/CompilationTest.sol
+1 −1 test/compilation/CompilationTestBase.sol
+8 −0 test/fixtures/test.json
+6 −0 test/fixtures/test.toml
+0 −441 test/mocks/MockERC20.t.sol
+0 −721 test/mocks/MockERC721.t.sol
2 changes: 1 addition & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
FreshCryptoLib/=lib/webauthn-sol/lib/FreshCryptoLib/solidity/src/
account-abstraction/=lib/account-abstraction/contracts/
ds-test/=lib/forge-std/lib/ds-test/src/
ds-test/=lib/p256-verifier/lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
Expand Down
6 changes: 6 additions & 0 deletions snapshots/EndToEndTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"e2e_transfer_erc20_baseAccount": "142302",
"e2e_transfer_erc20_eoa": "39910",
"e2e_transfer_native_baseAccount": "134635",
"e2e_transfer_native_eoa": "9338"
}
142 changes: 142 additions & 0 deletions test/gas/EndToEnd.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we order imports according to the CB solidity style guide? link to imports guide here


/// @title EndToEndTest
/// @notice Gas comparison tests between ERC-4337 Base Account and EOA transactions
/// @dev Isolated test contract to measure gas consumption for common operations
/// Tests ran using `FOUNDRY_PROFILE=deploy` to simulate real-world gas costs
/// forge-config: default.isolate = true
contract EndToEndTest is SmartWalletTestBase {
address eoaUser = address(0xe0a);
MockERC20 usdc;
MockTarget target;
CoinbaseSmartWalletFactory factory;

function setUp() public override {
// Deploy EntryPoint at canonical address
vm.etch(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789, Static.ENTRY_POINT_BYTES);

// Deploy smart wallet infrastructure
CoinbaseSmartWallet implementation = new CoinbaseSmartWallet();
factory = new CoinbaseSmartWalletFactory(address(implementation));

// Configure wallet owner
signerPrivateKey = 0xa11ce;
signer = vm.addr(signerPrivateKey);
owners.push(abi.encode(signer));
account = factory.createAccount(owners, 0);

// Fund wallets with ETH
vm.deal(address(account), 100 ether);
vm.deal(eoaUser, 100 ether);

// Deploy and mint USDC tokens
usdc = new MockERC20("USD Coin", "USDC", 6);
usdc.mint(address(account), 10000e6);
usdc.mint(eoaUser, 10000e6);

target = new MockTarget();
}

// Native ETH Transfer - Base Account
function test_transfer_native_baseAccount() public {
// Dust recipient to avoid gas changes for first non-zero balance
vm.deal(address(0x1234), 1 wei);

// Prepare UserOperation for native ETH transfer
userOpCalldata = abi.encodeCall(CoinbaseSmartWallet.execute, (address(0x1234), 1 ether, ""));
UserOperation memory op = _getUserOpWithSignature();

// Measure calldata size
bytes memory handleOpsCalldata = abi.encodeCall(entryPoint.handleOps, (_makeOpsArray(op), payable(bundler)));
console2.log("test_transfer_native Base Account calldata size:", handleOpsCalldata.length);

// Execute and measure gas
vm.startSnapshotGas("e2e_transfer_native_baseAccount");
_sendUserOperation(op);
uint256 gasUsed = vm.stopSnapshotGas();
console2.log("test_transfer_native Base Account gas:", gasUsed);
}

// Native ETH Transfer - EOA
function test_transfer_native_eoa() public {
// Dust recipient to avoid gas changes for first non-zero balance
vm.deal(address(0x1234), 1 wei);

console2.log("test_transfer_native EOA calldata size:", uint256(0));

// Execute and measure gas
vm.prank(eoaUser);
vm.startSnapshotGas("e2e_transfer_native_eoa");
payable(address(0x1234)).transfer(1 ether);
uint256 gasUsed = vm.stopSnapshotGas();
console2.log("test_transfer_native EOA gas:", gasUsed);
}

// ERC20 Transfer - Base Account
function test_transfer_erc20_baseAccount() public {
// Dust recipient to avoid gas changes for first non-zero balance
vm.deal(address(0x5678), 1 wei);
usdc.mint(address(0x5678), 1 wei);

// Prepare UserOperation for ERC20 transfer
userOpCalldata = abi.encodeCall(
CoinbaseSmartWallet.execute, (address(usdc), 0, abi.encodeCall(usdc.transfer, (address(0x5678), 100e6)))
);
UserOperation memory op = _getUserOpWithSignature();

// Measure calldata size
bytes memory handleOpsCalldata = abi.encodeCall(entryPoint.handleOps, (_makeOpsArray(op), payable(bundler)));
console2.log("test_transfer_erc20 Base Account calldata size:", handleOpsCalldata.length);

// Execute and measure gas
vm.startSnapshotGas("e2e_transfer_erc20_baseAccount");
_sendUserOperation(op);
uint256 gasUsed = vm.stopSnapshotGas();
console2.log("test_transfer_erc20 Base Account gas:", gasUsed);
}

// ERC20 Transfer - EOA
function test_transfer_erc20_eoa() public {
// Dust recipient to avoid gas changes for first non-zero balance
vm.deal(address(0x5678), 1 wei);
usdc.mint(address(0x5678), 1 wei);

// Measure calldata size
bytes memory eoaCalldata = abi.encodeCall(usdc.transfer, (address(0x5678), 100e6));
console2.log("test_transfer_erc20 EOA calldata size:", eoaCalldata.length);

// Execute and measure gas
vm.prank(eoaUser);
vm.startSnapshotGas("e2e_transfer_erc20_eoa");
usdc.transfer(address(0x5678), 100e6);
uint256 gasUsed = vm.stopSnapshotGas();
console2.log("test_transfer_erc20 EOA gas:", gasUsed);
}

// Helper Functions
// Creates an array containing a single UserOperation
function _makeOpsArray(UserOperation memory op) internal pure returns (UserOperation[] memory) {
UserOperation[] memory ops = new UserOperation[](1);
ops[0] = op;
return ops;
}

// Signs a UserOperation with the configured signer
// Overrides the parent implementation to use the configured signer
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)));
}
}
Loading