Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f743d2a
feat: add native token spend limit plugin
howydev Jun 12, 2024
3476930
chore: update
howydev Jun 27, 2024
4a1302d
fix: tests
howydev Jun 27, 2024
797c4ef
chore: remove permission hooks, add new internal helper function for …
howydev Jun 28, 2024
29853e2
fix: move to associated storage
howydev Jul 8, 2024
834e2a4
feat: add special paymaster list to still count towards limits
howydev Jul 8, 2024
b859519
c
howydev Jul 10, 2024
8862e91
fix: lint
howydev Jul 10, 2024
28ed73b
fix: lint, test
howydev Jul 10, 2024
6b2edbb
fix: test
howydev Jul 16, 2024
edeb0fa
[5/n permissions] feat: add erc20 token spend limit plugin (#80)
howydev Jul 16, 2024
24cc733
feat: Validation revamp - Introduce validation composability and allo…
fangting-alchemy Jul 17, 2024
39d327e
Allow direct plugin calls with validation & permission hooks (#90)
Zer0dot Jul 17, 2024
1f43a23
style: [v0.8-develop] linter and fmt update (#98)
adamegyed Jul 17, 2024
acfb938
Zer0dot/remove redundancy (#99)
Zer0dot Jul 17, 2024
151b056
fix: Add back pluginEntityLib test and update old references and docs…
fangting-alchemy Jul 17, 2024
90540d6
fix: Rename plugin to module and update readme and docs (#106)
fangting-alchemy Jul 19, 2024
ee292ad
fix: organize all modules under modules folder (#107)
fangting-alchemy Jul 19, 2024
5cb215f
feat: [v0.8-develop] User controlled install 1/N (#101)
adamegyed Jul 23, 2024
9ebbe95
feat: [v0.8-develop] merge plugin manager contracts 2/N (#102)
adamegyed Jul 23, 2024
547c8b4
feat: add factory
howydev Jul 18, 2024
0fa034a
update: add ownable, access control
howydev Jul 18, 2024
182d024
chore: comments
howydev Jul 18, 2024
2a14f7b
fix: lint
howydev Jul 19, 2024
ce0484e
test: add tests, cleanup
howydev Jul 19, 2024
d5667b5
chore: use immutable single signer validation
howydev Jul 19, 2024
125920f
fix: pr review fixes
howydev Jul 22, 2024
16b6e97
feat: [v0.8-develop] Remove validation installation from the manifest…
adamegyed Jul 23, 2024
6d928b6
feat: [v0.8-develop] HookConfig install parameter & internal structur…
adamegyed Jul 24, 2024
193e156
test: [v0.8-develop] Add test for ValidationConfigLib 5/N (#110)
adamegyed Jul 24, 2024
7f75b1e
fix: Handle fallback hooks & entrypoint/self-call/public selector run…
Zer0dot Jul 25, 2024
bfeffa3
chore: [v0.8-develop] alpha.0 deploy prep (#111)
adamegyed Jul 26, 2024
1dc64e5
fix: remove unnecessary foundry.toml config (#114)
adamegyed Jul 26, 2024
2bc08b0
fix: start broadcast in script (#115)
adamegyed Jul 26, 2024
3f562f5
Refactor: split IExecution out from IModule and rename vars to refle…
fangting-alchemy Jul 26, 2024
2e9878f
feat: add alpha.0 deployments (#116)
adamegyed Jul 26, 2024
ff4acbf
feat: implement single signer fallback validation
Zer0dot Jul 31, 2024
1d9eac1
chore: use custom error for fallback signer mismatch
Zer0dot Jul 31, 2024
32014dd
chore: modify create function visibility
Zer0dot Jul 31, 2024
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
20 changes: 20 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

# Factory owner capable only of managing stake
OWNER=
# EP 0.7 address
ENTRYPOINT=

# Create2 expected addresses of the contracts.
# When running for the first time, the error message will contain the expected addresses.
ACCOUNT_IMPL=
FACTORY=
SINGLE_SIGNER_VALIDATION=

# Optional, defaults to bytes32(0)
ACCOUNT_IMPL_SALT=
FACTORY_SALT=
SINGLE_SIGNER_VALIDATION_SALT=

# Optional, defaults to 0.1 ether and 1 day, respectively
STAKE_AMOUNT=
UNSTAKE_DELAY=
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@ node_modules/
# Coverage
report/
lcov.info

# env vars
.env

# deployments
broadcast/**/run-latest.json
broadcast/**/dry-run/**/*

# misc
.DS_Store
**/.DS_Store
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/modular-account-libs"]
path = lib/modular-account-libs
url = https://github.com/erc6900/modular-account-libs
36 changes: 18 additions & 18 deletions .solhint-test.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"extends": "solhint:recommended",
"rules": {
"func-name-mixedcase": "off",
"immutable-vars-naming": ["error"],
"no-unused-import": ["error"],
"compiler-version": ["error", ">=0.8.19"],
"custom-errors": "off",
"func-visibility": ["error", { "ignoreConstructors": true }],
"max-line-length": ["error", 120],
"max-states-count": ["warn", 30],
"modifier-name-mixedcase": ["error"],
"private-vars-leading-underscore": ["error"],
"no-inline-assembly": "off",
"avoid-low-level-calls": "off",
"one-contract-per-file": "off",
"no-empty-blocks": "off"
}
"extends": "solhint:recommended",
"rules": {
"func-name-mixedcase": "off",
"immutable-vars-naming": ["error"],
"no-unused-import": ["error"],
"compiler-version": ["error", ">=0.8.19"],
"custom-errors": "off",
"func-visibility": ["error", { "ignoreConstructors": true }],
"max-line-length": ["error", 120],
"max-states-count": ["warn", 30],
"modifier-name-mixedcase": ["error"],
"private-vars-leading-underscore": ["error"],
"no-inline-assembly": "off",
"avoid-low-level-calls": "off",
"one-contract-per-file": "off",
"no-empty-blocks": "off",
"reason-string": ["warn", { "maxLength": 64 }]
}
}
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Reference implementation for [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900). It is an early draft implementation.

The implementation includes an upgradable modular account with two plugins (`SingleOwnerPlugin` and `TokenReceiverPlugin`). It is compliant with ERC-6900 with the latest updates.
The implementation includes an upgradable modular account with three modules (`SingleSignerValidation`, `TokenReceiverModule`, and `AllowlistModule`). It is compliant with ERC-6900 with the latest updates.

## Important Callouts

Expand All @@ -11,7 +11,7 @@ The implementation includes an upgradable modular account with two plugins (`Sin

## Development

Anyone is welcome to submit feedback and/or PRs to improve code or add Plugins.
Anyone is welcome to submit feedback and/or PRs to improve code.

### Testing

Expand All @@ -28,3 +28,14 @@ Since IR compilation generates different bytecode, it's useful to test against t
FOUNDRY_PROFILE=optimized-build forge build
FOUNDRY_PROFILE=optimized-test forge test -vvv
```

## Integration testing

The reference implementation provides a sample factory and deploy script for the factory, account implementation, and the demo validation module `SingleSignerValidation`. This is not auditted, nor intended for production use. Limitations set by the GPL-V3 license apply.

To run this script, provide appropriate values in a `.env` file based on the `.env.example` template, then run:
```bash
forge script script/Deploy.s.sol <wallet options> -r <rpc_url> --broadcast
```

Where `<wallet_options>` specifies a way to sign the deployment transaction (see [here](https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---raw)) and `<rpc_url>` specifies an RPC for the network you are deploying on.
214 changes: 214 additions & 0 deletions broadcast/Deploy.s.sol/421614/run-1722008916.json

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions deployments/arb-sepolia.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Arbitrum Sepolia

Chain ID: 421614

## AccountFactory

| Version | Address | Explorer | Salt |
| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---- |
| v0.8.0-alpha.0 | `0x1c7EF41AA9896b74223a3956c7dDE28F206E8b24` | [explorer](https://sepolia.arbiscan.io/address/0x1c7EF41AA9896b74223a3956c7dDE28F206E8b24) | `0` |

## UpgradeableModularAccount Implementation

| Version | Address | Explorer | Salt |
| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---- |
| v0.8.0-alpha.0 | `0x0809BF385117a43A322A4E31d459c0EcaA3B1A08` | [explorer](https://sepolia.arbiscan.io/address/0x0809BF385117a43A322A4E31d459c0EcaA3B1A08) | `0` |

## SingleSignerValidation

| Version | Address | Explorer | Salt |
| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---- |
| v0.8.0-alpha.0 | `0x9DA8c098A483E257dd96022831DF308cB24fCBE6` | [explorer](https://sepolia.arbiscan.io/address/0x9DA8c098A483E257dd96022831DF308cB24fCBE6) | `0` |
17 changes: 8 additions & 9 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ libs = ['lib']
out = 'out'
optimizer = true
optimizer_runs = 200
auto_detect_solc = false
bytecode_hash = "none"
auto_detect_remappings = false
fs_permissions = [
{ access = "read", path = "./out-optimized" }
]
Expand All @@ -22,6 +25,7 @@ depth = 10
[profile.optimized-build]
via_ir = true
test = 'src'
optimizer_runs = 20000
out = 'out-optimized'

[profile.optimized-test]
Expand All @@ -38,7 +42,7 @@ runs = 5000
depth = 32

[profile.deep.fuzz]
runs = 10000
runs = 100000

[profile.deep.invariant]
runs = 5000
Expand All @@ -47,13 +51,8 @@ depth = 32
[fmt]
line_length = 115
wrap_comments = true

[rpc_endpoints]
mainnet = "${RPC_URL_MAINNET}"
goerli = "${RPC_URL_GOERLI}"

[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
goerli = { key = "${ETHERSCAN_API_KEY}" }
sort_imports = true
number_underscore = "thousands"
int_types = "long"

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
1 change: 1 addition & 0 deletions lib/modular-account-libs
Submodule modular-account-libs added at 5d9d0e
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},
"scripts": {
"lint": "pnpm lint:src && pnpm lint:test",
"lint:src": "solhint -c .solhint-src.json './src/**/*.sol'",
"lint:test": "solhint -c .solhint-test.json './test/**/*.sol'"
"lint:src": "solhint --max-warnings 0 -c .solhint-src.json './src/**/*.sol'",
"lint:test": "solhint --max-warnings 0 -c .solhint-test.json './test/**/*.sol'"
}
}
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
@eth-infinitism/account-abstraction/=lib/account-abstraction/contracts/
@openzeppelin/=lib/openzeppelin-contracts/
@modular-account-libs/=lib/modular-account-libs/src/
162 changes: 162 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/Test.sol";

import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";

import {AccountFactory} from "../src/account/AccountFactory.sol";
import {UpgradeableModularAccount} from "../src/account/UpgradeableModularAccount.sol";
import {SingleSignerValidation} from "../src/modules/validation/SingleSignerValidation.sol";

contract DeployScript is Script {
IEntryPoint public entryPoint = IEntryPoint(payable(vm.envAddress("ENTRYPOINT")));

address public owner = vm.envAddress("OWNER");

address public accountImpl = vm.envOr("ACCOUNT_IMPL", address(0));
address public factory = vm.envOr("FACTORY", address(0));
address public singleSignerValidation = vm.envOr("SINGLE_SIGNER_VALIDATION", address(0));

bytes32 public accountImplSalt = bytes32(vm.envOr("ACCOUNT_IMPL_SALT", uint256(0)));
bytes32 public factorySalt = bytes32(vm.envOr("FACTORY_SALT", uint256(0)));
bytes32 public singleSignerValidationSalt = bytes32(vm.envOr("SINGLE_SIGNER_VALIDATION_SALT", uint256(0)));

uint256 public requiredStakeAmount = vm.envOr("STAKE_AMOUNT", uint256(0.1 ether));
uint256 public requiredUnstakeDelay = vm.envOr("UNSTAKE_DELAY", uint256(1 days));

function run() public {
console2.log("******** Deploying ERC-6900 Reference Implementation ********");
console2.log("Chain: ", block.chainid);
console2.log("EP: ", address(entryPoint));
console2.log("Factory owner: ", owner);

vm.startBroadcast();
_deployAccountImpl(accountImplSalt, accountImpl);
_deploySingleSignerValidation(singleSignerValidationSalt, singleSignerValidation);
_deployAccountFactory(factorySalt, factory);
_addStakeForFactory(uint32(requiredUnstakeDelay), requiredStakeAmount);
vm.stopBroadcast();
}

function _deployAccountImpl(bytes32 salt, address expected) internal {
console2.log(string.concat("Deploying AccountImpl with salt: ", vm.toString(salt)));

address addr = Create2.computeAddress(
salt,
keccak256(abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(entryPoint))),
CREATE2_FACTORY
);
if (addr != expected) {
console2.log("Expected address mismatch");
console2.log("Expected: ", expected);
console2.log("Actual: ", addr);
revert();
}

if (addr.code.length == 0) {
console2.log("No code found at expected address, deploying...");
UpgradeableModularAccount deployed = new UpgradeableModularAccount{salt: salt}(entryPoint);

if (address(deployed) != expected) {
console2.log("Deployed address mismatch");
console2.log("Expected: ", expected);
console2.log("Deployed: ", address(deployed));
revert();
}

console2.log("Deployed AccountImpl at: ", address(deployed));
} else {
console2.log("Code found at expected address, skipping deployment");
}
}

function _deploySingleSignerValidation(bytes32 salt, address expected) internal {
console2.log(string.concat("Deploying SingleSignerValidation with salt: ", vm.toString(salt)));

address addr = Create2.computeAddress(
salt, keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), CREATE2_FACTORY
);
if (addr != expected) {
console2.log("Expected address mismatch");
console2.log("Expected: ", expected);
console2.log("Actual: ", addr);
revert();
}

if (addr.code.length == 0) {
console2.log("No code found at expected address, deploying...");
SingleSignerValidation deployed = new SingleSignerValidation{salt: salt}();

if (address(deployed) != expected) {
console2.log("Deployed address mismatch");
console2.log("Expected: ", expected);
console2.log("Deployed: ", address(deployed));
revert();
}

console2.log("Deployed SingleSignerValidation at: ", address(deployed));
} else {
console2.log("Code found at expected address, skipping deployment");
}
}

function _deployAccountFactory(bytes32 salt, address expected) internal {
console2.log(string.concat("Deploying AccountFactory with salt: ", vm.toString(salt)));

address addr = Create2.computeAddress(
salt,
keccak256(
abi.encodePacked(
type(AccountFactory).creationCode,
abi.encode(entryPoint, accountImpl, singleSignerValidation, owner)
)
),
CREATE2_FACTORY
);
if (addr != expected) {
console2.log("Expected address mismatch");
console2.log("Expected: ", expected);
console2.log("Actual: ", addr);
revert();
}

if (addr.code.length == 0) {
console2.log("No code found at expected address, deploying...");
AccountFactory deployed = new AccountFactory{salt: salt}(
entryPoint, UpgradeableModularAccount(payable(accountImpl)), singleSignerValidation, owner
);

if (address(deployed) != expected) {
console2.log("Deployed address mismatch");
console2.log("Expected: ", expected);
console2.log("Deployed: ", address(deployed));
revert();
}

console2.log("Deployed AccountFactory at: ", address(deployed));
} else {
console2.log("Code found at expected address, skipping deployment");
}
}

function _addStakeForFactory(uint32 unstakeDelay, uint256 stakeAmount) internal {
console2.log("Adding stake to factory");

uint256 currentStake = entryPoint.getDepositInfo(address(factory)).stake;
console2.log("Current stake: ", currentStake);
uint256 stakeToAdd = stakeAmount - currentStake;

if (stakeToAdd > 0) {
console2.log("Adding stake: ", stakeToAdd);
entryPoint.addStake{value: stakeToAdd}(unstakeDelay);
console2.log("Staked factory: ", address(factory));
console2.log("Total stake amount: ", entryPoint.getDepositInfo(address(factory)).stake);
console2.log("Unstake delay: ", entryPoint.getDepositInfo(address(factory)).unstakeDelaySec);
} else {
console2.log("No stake to add");
}
}
}
Loading