Skip to content

Commit

Permalink
Only Ethereum
Browse files Browse the repository at this point in the history
  • Loading branch information
PrashikshitSaini committed Oct 6, 2024
1 parent db3f8d6 commit 2118358
Show file tree
Hide file tree
Showing 13 changed files with 412 additions and 123 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/account-abstraction"]
path = lib/account-abstraction
url = https://github.com/eth-infinitism/account-abstraction
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/openzeppelin/openzeppelin-contracts
74 changes: 8 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,8 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
# About

1. Create a basic AA on Ethereum.
2. Create a basic AA on zkSync.
3. Deplot and send a UserOp/transaction through them.
1. not going to send an AA to Ethereum.
2. But we will send an AA tx to zkSync.

1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
src = "src"
out = "out"
libs = ["lib"]
remappings = ['@openzeppelin/=lib/openzeppelin-contracts/']

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions lib/account-abstraction
Submodule account-abstraction added at 7af70c
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at dbb610
19 changes: 0 additions & 19 deletions script/Counter.s.sol

This file was deleted.

22 changes: 22 additions & 0 deletions script/DeployMinimal.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.24;

import {Script} from "forge-std/Script.sol";
import {MinimalAccount} from "src/Ethereum/MinimalAccount.sol";
import {HelperConfig} from "script/HelperConfig.s.sol";

contract DeployMinimal is Script {
function run() public {}

function deployMinimalAccount() public returns (HelperConfig, MinimalAccount) {
HelperConfig helperConfig = new HelperConfig();
HelperConfig.NetworkConfig memory config = helperConfig.getConfig();

vm.startBroadcast(config.account);
MinimalAccount minimalAccount = new MinimalAccount(config.entryPoint);
minimalAccount.transferOwnership(config.account);
vm.stopBroadcast();

return (helperConfig, minimalAccount);
}
}
66 changes: 66 additions & 0 deletions script/HelperConfig.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.24;

import {Script, console2} from "forge-std/Script.sol";
import {EntryPoint} from "lib/account-abstraction/contracts/core/EntryPoint.sol";

contract HelperConfig is Script {
error HelperConfig__InvalidChainId();

struct NetworkConfig {
address entryPoint;
address account;
}

uint256 constant ETH_SEPLOIA_CHAIN_ID = 11155111;
uint256 constant ZKSYNC_SEPOLIA_CHAIN_ID = 300;
uint256 constant LOCAL_CHAIN_ID = 31337;
address constant BURNER_WALLET = 0x4066791b6fB2E2A4F36E49292FbF7bbBC22F8927;
// address constant FOUNDRY_DEFAULT_WALLET = 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38;
address constant ANVIL_DEFAULT_WALLET = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;

NetworkConfig public localNetworkConfig;
mapping(uint256 chainId => NetworkConfig) public networkConfigs;

constructor() {
networkConfigs[ETH_SEPLOIA_CHAIN_ID] = getEthSepoliaConfig();
}

function getConfig() public returns (NetworkConfig memory) {
return getConfigByChainId(block.chainid);
}

function getConfigByChainId(uint256 chainId) public returns (NetworkConfig memory) {
if (chainId == LOCAL_CHAIN_ID) {
return getOrCreateAnvilConfig();
} else if (networkConfigs[chainId].account != address(0)) {
return networkConfigs[chainId];
} else {
revert HelperConfig__InvalidChainId();
}
}

function getEthSepoliaConfig() public pure returns (NetworkConfig memory) {
return NetworkConfig({entryPoint: address(0), account: BURNER_WALLET});
}

function getZkySyncSepoliaConfig() public pure returns (NetworkConfig memory) {
return NetworkConfig({entryPoint: address(0), account: BURNER_WALLET});
}

function getOrCreateAnvilConfig() public returns (NetworkConfig memory) {
if (localNetworkConfig.account != address(0)) {
return localNetworkConfig;
}

// Deploy Mocks
console2.log("Deploying Mocks...");
vm.startBroadcast(ANVIL_DEFAULT_WALLET);
EntryPoint entryPoint = new EntryPoint();
vm.stopBroadcast();

localNetworkConfig = NetworkConfig({entryPoint: address(entryPoint), account: ANVIL_DEFAULT_WALLET});

return localNetworkConfig;
}
}
83 changes: 83 additions & 0 deletions script/SendPackedUserOp.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Script} from "forge-std/Script.sol";
import {MinimalAccount} from "src/Ethereum/MinimalAccount.sol";
import {PackedUserOperation} from "lib/account-abstraction/contracts/interfaces/PackedUserOperation.sol";
import {HelperConfig} from "script/HelperConfig.s.sol";
import {IEntryPoint} from "lib/account-abstraction/contracts/interfaces/IEntryPoint.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract SendPackedUserOp is Script {
using MessageHashUtils for bytes32;

function run() public {
// HelperConfig helperConfig = new HelperConfig();
// address dest = helperConfig.getConfig().entryPoint;
// uint256 value = 0;
// bytes memory functionData =
// abi.encodeWithSelector(IERC20.approve.selector, 0x0000000000000000000000000000000000000000, 1e18);
// bytes memory executeCallData =
// abi.encodeWithSelector(MinimalAccount.execute.selector, dest, value, functionData);
// PackedUserOperation memory userOp = generateSignedUserOperation(
// executeCallData, helperConfig.getConfig(), 0x0000000000000000000000000000000000000000
// );
// PackedUserOperation[] memory ops = new PackedUserOperation[](1);
// ops[0] = userOp;

// vm.startBroadcast();
// IEntryPoint(helperConfig.getConfig().entryPoint).handleOps(ops, helperConfig.getConfig().account);
}

function generateSignedUserOperation(
bytes memory callData,
HelperConfig.NetworkConfig memory config,
address minimalAccount
) public view returns (PackedUserOperation memory) {
// 1. Generate The unsigned Data
uint256 nonce = vm.getNonce(minimalAccount) - 1;
PackedUserOperation memory userOp = generateUnsignedUserOperation(callData, minimalAccount, nonce);

// 2. Get the UserOp Hash

bytes32 userOpHash = IEntryPoint(config.entryPoint).getUserOpHash(userOp);
bytes32 digest = userOpHash.toEthSignedMessageHash();

// 3. Sign the data and return it
uint8 v;
bytes32 r;
bytes32 s;
uint256 ANVIL_DEFAULT_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
if (block.chainid == 31337) {
(v, r, s) = vm.sign(ANVIL_DEFAULT_KEY, digest);
} else {
(v, r, s) = vm.sign(config.account, digest);
}
userOp.signature = abi.encodePacked(r, s, v);
return userOp;
}

function generateUnsignedUserOperation(bytes memory callData, address sender, uint256 nonce)
internal
pure
returns (PackedUserOperation memory)
{
uint128 verificationGasLimit = 16777216;
uint128 callGasLimit = verificationGasLimit;
uint128 maxPriorityFeePerGas = 256;
uint128 maxFeePerGas = maxPriorityFeePerGas;

return PackedUserOperation({
sender: sender,
nonce: nonce,
initCode: hex"",
callData: callData,
accountGasLimits: bytes32(uint256(verificationGasLimit) << 128 | callGasLimit),
preVerificationGas: verificationGasLimit,
gasFees: bytes32(uint256(maxPriorityFeePerGas) << 128 | maxFeePerGas),
paymasterAndData: hex"",
signature: hex""
});
}
}
14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

83 changes: 83 additions & 0 deletions src/Ethereum/MinimalAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IAccount} from "lib/account-abstraction/contracts/interfaces/IAccount.sol";
import {PackedUserOperation} from "lib/account-abstraction/contracts/interfaces/PackedUserOperation.sol";
import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {SIG_VALIDATION_FAILED, SIG_VALIDATION_SUCCESS} from "lib/account-abstraction/contracts/core/Helpers.sol";
import {IEntryPoint} from "lib/account-abstraction/contracts/interfaces/IEntryPoint.sol";

contract MinimalAccount is IAccount, Ownable {
error MinimalAccount__NotFromEntryPoint();
error MinimalAccount__NotFromEntryPointOrOwner();
error MinimalAccount__CallFailed(bytes);

IEntryPoint private immutable i_entryPoint;

modifier requireFromEntryPoint() {
if (msg.sender != address(i_entryPoint)) {
revert MinimalAccount__NotFromEntryPoint();
}
_;
}

modifier requireFromEntryPointOrOwner() {
if (msg.sender != address(i_entryPoint) && msg.sender != owner()) {
revert MinimalAccount__NotFromEntryPointOrOwner();
}
_;
}

constructor(address entryPoint) Ownable(msg.sender) {
i_entryPoint = IEntryPoint(entryPoint);
}

receive() external payable {}

function execute(address dest, uint256 value, bytes calldata functionData) external requireFromEntryPointOrOwner {
(bool success, bytes memory result) = dest.call{value: value}(functionData);
if (!success) {
revert MinimalAccount__CallFailed(result);
}
}

// this is the function that will be called by the entrypoint to validate the user of the transaction.
// A signature is valid if its the account owner
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
external
returns (uint256 validationData)
{
//
validationData = _validateSignature(userOp, userOpHash);
_payPrefund(missingAccountFunds);
}

// userOphash will be the EIP-191 version of the signed hash
// This function is just like checking the condition for the transactions from our account, like checking the google auth or checking if all my friends have signed the message
function _validateSignature(PackedUserOperation calldata userOp, bytes32 userOpHash)
internal
view
returns (uint256 validationData)
{
bytes32 ethSignedMessageHash = MessageHashUtils.toEthSignedMessageHash(userOpHash);
address signer = ECDSA.recover(ethSignedMessageHash, userOp.signature);
if (signer != owner()) {
return SIG_VALIDATION_FAILED;
}
return SIG_VALIDATION_SUCCESS;
}

function _payPrefund(uint256 missingAccountFunds) internal {
if (missingAccountFunds != 0) {
(bool success,) = payable(msg.sender).call{value: missingAccountFunds, gas: type(uint256).max}("");
(success);
}
}

//
function getEntryPoint() public view returns (address) {
return address(i_entryPoint);
}
}
Loading

0 comments on commit 2118358

Please sign in to comment.