Skip to content

Commit 0aebaba

Browse files
committed
init
0 parents  commit 0aebaba

11 files changed

Lines changed: 378 additions & 0 deletions

File tree

.github/workflows/test.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: CI
2+
3+
permissions: {}
4+
5+
on:
6+
push:
7+
pull_request:
8+
workflow_dispatch:
9+
10+
env:
11+
FOUNDRY_PROFILE: ci
12+
13+
jobs:
14+
check:
15+
name: Foundry project
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
steps:
20+
- uses: actions/checkout@v5
21+
with:
22+
persist-credentials: false
23+
submodules: recursive
24+
25+
- name: Install Foundry
26+
uses: foundry-rs/foundry-toolchain@v1
27+
28+
- name: Show Forge version
29+
run: forge --version
30+
31+
- name: Run Forge fmt
32+
run: forge fmt --check
33+
34+
- name: Run Forge build
35+
run: forge build --sizes
36+
37+
- name: Run Forge tests
38+
run: forge test -vvv

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Compiler files
2+
cache/
3+
out/
4+
5+
# Ignores development broadcast logs
6+
!/broadcast
7+
/broadcast/*/31337/
8+
/broadcast/**/dry-run/
9+
10+
# Docs
11+
docs/
12+
13+
# Dotenv file
14+
.env

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "lib/forge-std"]
2+
path = lib/forge-std
3+
url = https://github.com/foundry-rs/forge-std

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
## Foundry
2+
3+
**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
4+
5+
Foundry consists of:
6+
7+
- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
8+
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
9+
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
10+
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
11+
12+
## Documentation
13+
14+
https://book.getfoundry.sh/
15+
16+
## Usage
17+
18+
### Build
19+
20+
```shell
21+
$ forge build
22+
```
23+
24+
### Test
25+
26+
```shell
27+
$ forge test
28+
```
29+
30+
### Format
31+
32+
```shell
33+
$ forge fmt
34+
```
35+
36+
### Gas Snapshots
37+
38+
```shell
39+
$ forge snapshot
40+
```
41+
42+
### Anvil
43+
44+
```shell
45+
$ anvil
46+
```
47+
48+
### Deploy
49+
50+
```shell
51+
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
52+
```
53+
54+
### Cast
55+
56+
```shell
57+
$ cast <subcommand>
58+
```
59+
60+
### Help
61+
62+
```shell
63+
$ forge --help
64+
$ anvil --help
65+
$ cast --help
66+
```

foundry.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"lib/forge-std": {
3+
"tag": {
4+
"name": "v1.14.0",
5+
"rev": "1801b0541f4fda118a10798fd3486bb7051c5dd6"
6+
}
7+
}
8+
}

foundry.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[profile.default]
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
6+
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

lib/forge-std

Submodule forge-std added at 1801b05

script/Counter.s.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import {Script} from "forge-std/Script.sol";
5+
import {Counter} from "../src/Counter.sol";
6+
7+
contract CounterScript is Script {
8+
Counter public counter;
9+
10+
function setUp() public {}
11+
12+
function run() public {
13+
vm.startBroadcast();
14+
15+
counter = new Counter();
16+
17+
vm.stopBroadcast();
18+
}
19+
}

src/AccountConfiguration.sol

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
interface IInitialized {
5+
function initialized() external view returns (bool);
6+
}
7+
8+
contract AccountConfiguration {
9+
enum OwnerType {
10+
ADDRESS,
11+
SECP256K1,
12+
SECP256R1,
13+
WEBAUTHN,
14+
BLS12381
15+
}
16+
17+
struct Owner {
18+
OwnerType ownerType;
19+
bytes ownerBytes;
20+
}
21+
22+
struct OwnerConfig {
23+
bool added;
24+
bool removed;
25+
}
26+
27+
struct OwnerChange {
28+
bool add;
29+
Owner owner;
30+
uint64 nonceSequence;
31+
}
32+
33+
bytes4 constant ADDRESS_OWNER = bytes4(keccak256("ADDRESS"));
34+
bytes4 constant SECP256K1_OWNER = bytes4(keccak256("SECP256K1"));
35+
bytes4 constant SECP256R1_OWNER = bytes4(keccak256("SECP256R1"));
36+
bytes4 constant WEBAUTHN_OWNER = bytes4(keccak256("WEBAUTHN"));
37+
bytes4 constant BLS12381_OWNER = bytes4(keccak256("BLS12381"));
38+
39+
uint196 constant REPLAY_NONCE_KEY = type(uint196).max;
40+
41+
/// @dev Inner mappings keyed by account address for ERC-7562 compliance
42+
uint256 counter;
43+
mapping(address account => uint256 index => bytes ownerBytes);
44+
mapping(address account => bytes owner => bool) isOwner;
45+
46+
mapping(bytes ownerBytes => mapping(address account => OwnerConfig config)) public ownerConfig;
47+
mapping(uint196 key => mapping(address account => uint64 sequence)) public nonce;
48+
49+
event AccountCreated(address indexed account, Owner[] owners, bytes32 bytecodeHash);
50+
event OwnerAdded(address indexed account, address ownerId, Owner owner);
51+
event OwnerRemoved(address indexed account, address ownerId);
52+
event NonceIncremented(address indexed account, uint256 indexed nonceKey, uint256 nonceSequence);
53+
54+
error InvalidOwner(Owner owner);
55+
error InvalidOwnerIndex(uint8 index);
56+
error AccountNotInitialized(address account);
57+
error NoOwnerAtIndex(address account, uint8 index);
58+
error NotAccountOwner(address account, Owner owner);
59+
60+
/// @notice Fallback function to delegatecall diamond, functions set by node
61+
function fallback() external {
62+
// delegatecall diamond
63+
}
64+
65+
/// @notice Designed for 7702 accounts to initialize on first use
66+
function initializeAccount() external {}
67+
68+
function createAccount(
69+
Owner[] calldata owners,
70+
uint256 nonce,
71+
bytes calldata bytecode,
72+
bytes calldata initializeCall // most helpful for ERC-1167 proxies
73+
) external returns (address account) {
74+
// Early return if account deployed
75+
account = computeAddress(owners, nonce, bytecode);
76+
if (account.code != 0) return;
77+
78+
// Configure intitial owners
79+
for (uint i; i < owners.length; i++) {
80+
if (!isValidOwner(owners[i])) revert InvalidOwner(owners[i]);
81+
ownerAtIndex[i][account] = owners[i];
82+
ownerConfig[computeOwnerId(owners[i])][account] = OwnerConfig({removed: false, index: i, nextIndex: i + 1});
83+
emit OwnerUpdated(account, i, owners[i]);
84+
}
85+
nextOwnerIndex[account] = owners.length;
86+
87+
// Create account
88+
bytes32 salt = computeSalt(owners, nonce);
89+
assembly {
90+
create2(0, add(bytecode, 0x20), mload(bytecode), salt)
91+
}
92+
emit AccountCreated(account, owners, keccak256(bytecode));
93+
94+
// Initialize
95+
account.call(initializeCall);
96+
97+
// Assert account is initialized to mitigate undeployed implementations
98+
if (!IInitialized(account).initialized()) revert AccountNotInitialized(account);
99+
}
100+
101+
function addOwner(Owner owner) external {
102+
// Validate owner
103+
if (!isValidOwner(owner)) revert InvalidOwner(owner);
104+
105+
// Clear previous owner if exists
106+
Owner memory previousOwner = ownerAtIndex[index][msg.sender];
107+
if (previousOwner.ownerId.length != 0) {
108+
ownerConfig[_hashOwner(previousOwner)][msg.sender];
109+
}
110+
111+
// Update owner configuration
112+
ownerAtIndex[index][msg.sender] = owner;
113+
ownerConfig[_hashOwner(owner)][msg.sender] = index + 1;
114+
emit OwnerUpdated(msg.sender, index, owner);
115+
}
116+
117+
function removeOwner(address ownerId) external {
118+
// Validate index
119+
if (index > 127) revert InvalidOwnerIndex(index);
120+
121+
// Check owner exists
122+
Owner memory owner = ownerAtIndex[index][msg.sender];
123+
if (owner.ownerId.length == 0) revert NoOwnerAtIndex(account, index);
124+
125+
// Remove owner configuration
126+
delete ownerAtIndex[index][msg.sender];
127+
ownerConfig[_hashOwner(owner)][msg.sender];
128+
emit OwnerRemoved(msg.sender, index)
129+
}
130+
131+
/// @notice Apply signed, replayable owner changes
132+
function applyOwnerChanges(address account, OwnerChange[] calldata ownerChanges) external {
133+
// validate signature
134+
135+
for (uint i; i < ownerChanges.length; i++) {
136+
if (nonce[REPLAY_NONCE_KEY][account] != ownerChanges[i].nonceSequence) revert();
137+
uint64 usedSequence = nonce[REPLAY_NONCE_KEY][account]++;
138+
emit NonceIncremented(account, REPLAY_NONCE_KEY, usedSequence);
139+
// update account owner configuration
140+
}
141+
142+
}
143+
144+
function isOwner(address account, Owner calldata owner) external view returns (bool ) {
145+
return indexOfOwner(account, owner) > 0;
146+
}
147+
148+
function computeAddress(Owner[] calldata owners, uint256 nonce, bytes calldata bytecode) public view returns (address) {
149+
bytes32 salt = computeSalt(owners, nonce);
150+
bytes32 bytecodeHash = keccak256(bytecode);
151+
bytes32 create2Hash = keccak256(abi.encodePacked(0xFF, address(this), salt, bytecodeHash));
152+
return address(uint160(uint256(create2Hash)));
153+
}
154+
155+
function computeSalt(Owner[] calldata owners, uint256 nonce) public pure returns (bytes32) {
156+
return keccak256(abi.encode(owners, nonce));
157+
}
158+
159+
function computeOwnerId(Owner memory owner) public pure returns (address ownerId) {
160+
if (owner.ownerType == ADDRESS_OWNER) {
161+
return address(bytes20(owner.ownerBytes));
162+
}
163+
return address(bytes20(bytes32(keccak256(abi.encode(owner.ownerBytes)))));
164+
}
165+
166+
function isValidOwner(Owner memory owner) public pure returns (bool valid) {
167+
uint256 assertLength;
168+
if (owner.ownerType == ADDRESS_OWNER) {
169+
assertLength = 20;
170+
} else if (
171+
owner.ownerType == SECP256K1_OWNER ||
172+
owner.ownerType == SECP256R1_OWNER ||
173+
owner.ownerType == WEBAUTHN_OWNER ||
174+
owner.ownerType == BLS12381_OWNER
175+
) {
176+
assertLength = 64;
177+
} else {
178+
return false;
179+
}
180+
181+
if (owner.ownerId.length != assertLength) return false;
182+
183+
return true;
184+
}
185+
}

src/Counter.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
contract Counter {
5+
uint256 public number;
6+
7+
function setNumber(uint256 newNumber) public {
8+
number = newNumber;
9+
}
10+
11+
function increment() public {
12+
number++;
13+
}
14+
}

0 commit comments

Comments
 (0)