-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
db3f8d6
commit 2118358
Showing
13 changed files
with
412 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule account-abstraction
added at
7af70c
Submodule openzeppelin-contracts
added at
dbb610
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"" | ||
}); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.