Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spec update 6 #18

Merged
merged 27 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a3a952d
Rename Execution to Call, and update IStandardExecutor
adamegyed Nov 28, 2023
0e4b0ab
Update IPluginExecutor NatSpec
adamegyed Nov 29, 2023
b4755df
Merge pull request #12 from erc6900/adam/istandardexecutor-update
adam-alchemy Nov 29, 2023
02f04fd
update IPluginManager
fangting-alchemy Nov 28, 2023
c06dd7f
update name IPluginLoupe to IAccountLoupe
fangting-alchemy Nov 28, 2023
3415be0
update IAccountLoupe interface to spec update 6
fangting-alchemy Nov 28, 2023
8382b42
update BaseModularAccount to PluginManagerInternals and clean it up
fangting-alchemy Nov 28, 2023
1b2386b
update BaseModularAccount to PluginManagerInternals and clean it up
fangting-alchemy Nov 28, 2023
c53849f
update IPlugin to match spec update 6
fangting-alchemy Nov 29, 2023
81c7a6c
add canSpendNativeToken support in code
fangting-alchemy Nov 29, 2023
2c8a124
re-arrange imports and inheritance
fangting-alchemy Nov 29, 2023
d9900e5
Merge pull request #13 from erc6900/ft_update
fangting-alchemy Nov 29, 2023
0e95e25
Remove uninstall field checking
adamegyed Nov 29, 2023
59c482a
Move exec hooks and permitted call hooks to hook group
adamegyed Nov 29, 2023
883e067
Merge pull request #15 from erc6900/adam/install-validity
adam-alchemy Nov 29, 2023
cc85a74
perf: precompile contracts for faster test runs
jaypaik Nov 29, 2023
ca45d90
Merge pull request #14 from erc6900/11-28-perf_precompile_contracts_f…
jaypaik Nov 30, 2023
e49abb7
Add post-only hooks and related tests
adamegyed Nov 30, 2023
4d43b58
Merge branch 'spec-update-6' into adam/post-only-hooks
adamegyed Nov 30, 2023
075bc7a
Update to use optimized test
adamegyed Nov 30, 2023
8ed1304
Fix pre exec hook data in executeFromPlugin
adamegyed Nov 30, 2023
50e6d3e
Fix lint
adamegyed Nov 30, 2023
ae34bb4
Fix via-IR build & refactor
adamegyed Nov 30, 2023
5ad698e
Merge pull request #16 from erc6900/adam/post-only-hooks
adam-alchemy Nov 30, 2023
f6bc217
feat: allow overlapping hooks
jaypaik Nov 30, 2023
c57b486
feat: update ordering of associated post hook executions
jaypaik Dec 1, 2023
42c6056
Merge pull request #17 from erc6900/11-30-feat_allow_overlapping_hooks
jaypaik Dec 1, 2023
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: 10 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: ERC6900 RI Test CI
name: ERC-6900 RI Test CI

on: [pull_request, workflow_dispatch]

Expand Down Expand Up @@ -45,9 +45,9 @@ jobs:

- name: "Lint the contracts"
run: "pnpm lint"
test:
name: Run Forge Tests

test-optimized-test-deep:
name: Run Forge Tests (optimized-test-deep)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -63,13 +63,13 @@ jobs:
run: forge install

- name: Build project
run: forge build
run: FOUNDRY_PROFILE=optimized-build forge build

- name: Run tests
run: FOUNDRY_PROFILE=deep forge test -vvv
run: FOUNDRY_PROFILE=optimized-test-deep forge test -vvv

test-lite:
name: Run Forge Tests [lite build]
test-default:
name: Run Forge Tests (default)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -85,7 +85,7 @@ jobs:
run: forge install

- name: Build project
run: FOUNDRY_PROFILE=lite forge build
run: forge build

- name: Run tests
run: FOUNDRY_PROFILE=lite forge test -vvv
run: forge test -vvv
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Foundry build and cache directories
out/
out-optimized/
cache/
node_modules/

# Coverage
report/
lcov.info
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ERC-6900 Ref Implementation
# ERC-6900 Reference Implementation

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

Expand All @@ -13,20 +13,18 @@ The implementation includes an upgradable modular account with two plugins (`Sin

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

### Build
### Testing

The default Foundry profile can be used to compile (without IR) and test the entire project. The default profile should be used when generating coverage and debugging.

```bash
forge build

# or use the lite profile to reduce compilation time
FOUNDRY_PROFILE=lite forge build
forge test -vvv
```

### Test
Since IR compilation generates different bytecode, it's useful to test against the contracts compiled via IR. Since compiling the entire project (including the test suite) takes a long time, special profiles can be used to precompile just the source contracts, and have the tests deploy the relevant contracts using those artifacts.

```bash
forge test -vvv

# or use the lite profile to reduce compilation time
FOUNDRY_PROFILE=lite forge test -vvv
FOUNDRY_PROFILE=optimized-build forge build
FOUNDRY_PROFILE=optimized-test forge test -vvv
```
32 changes: 23 additions & 9 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
[profile.default]
solc = '0.8.19'
via_ir = true
via_ir = false
src = 'src'
out = 'out'
test = 'test'
libs = ['lib']
out = 'out'
optimizer = true
optimizer_runs = 10_000
ignored_error_codes = [3628]
fs_permissions = [
{ access = "read", path = "./out-optimized" }
]

[fuzz]
runs = 500
Expand All @@ -17,12 +20,23 @@ runs=500
fail_on_revert = true
depth = 10

[profile.lite]
solc = '0.8.19'
via_ir = false
optimizer = true
optimizer_runs = 10_000
ignored_error_codes = [3628]
[profile.optimized-build]
via_ir = true
test = 'src'
out = 'out-optimized'

[profile.optimized-test]
src = 'test'

[profile.optimized-test-deep]
src = 'test'

[profile.optimized-test-deep.fuzz]
runs = 10000

[profile.optimized-test-deep.invariant]
runs = 5000
depth = 32

[profile.deep.fuzz]
runs = 10000
Expand All @@ -43,4 +57,4 @@ goerli = "${RPC_URL_GOERLI}"
mainnet = { key = "${ETHERSCAN_API_KEY}" }
goerli = { key = "${ETHERSCAN_API_KEY}" }

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
148 changes: 148 additions & 0 deletions src/account/AccountLoupe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol";
import {IPluginManager} from "../interfaces/IPluginManager.sol";
import {IStandardExecutor} from "../interfaces/IStandardExecutor.sol";
import {
AccountStorage,
getAccountStorage,
getPermittedCallKey,
HookGroup,
toFunctionReferenceArray
} from "../libraries/AccountStorage.sol";
import {FunctionReference} from "../libraries/FunctionReferenceLib.sol";

abstract contract AccountLoupe is IAccountLoupe {
using EnumerableMap for EnumerableMap.Bytes32ToUintMap;
using EnumerableSet for EnumerableSet.AddressSet;

error ManifestDiscrepancy(address plugin);

/// @inheritdoc IAccountLoupe
function getExecutionFunctionConfig(bytes4 selector)
external
view
returns (ExecutionFunctionConfig memory config)
{
AccountStorage storage _storage = getAccountStorage();

if (
selector == IStandardExecutor.execute.selector || selector == IStandardExecutor.executeBatch.selector
|| selector == UUPSUpgradeable.upgradeTo.selector
|| selector == UUPSUpgradeable.upgradeToAndCall.selector
|| selector == IPluginManager.installPlugin.selector
|| selector == IPluginManager.uninstallPlugin.selector
) {
config.plugin = address(this);
} else {
config.plugin = _storage.selectorData[selector].plugin;
}

config.userOpValidationFunction = _storage.selectorData[selector].userOpValidation;

config.runtimeValidationFunction = _storage.selectorData[selector].runtimeValidation;
}

/// @inheritdoc IAccountLoupe
function getExecutionHooks(bytes4 selector) external view returns (ExecutionHooks[] memory execHooks) {
execHooks = _getHooks(getAccountStorage().selectorData[selector].executionHooks);
}

/// @inheritdoc IAccountLoupe
function getPermittedCallHooks(address callingPlugin, bytes4 selector)
external
view
returns (ExecutionHooks[] memory execHooks)
{
bytes24 key = getPermittedCallKey(callingPlugin, selector);
execHooks = _getHooks(getAccountStorage().permittedCalls[key].permittedCallHooks);
}

/// @inheritdoc IAccountLoupe
function getPreValidationHooks(bytes4 selector)
external
view
returns (
FunctionReference[] memory preUserOpValidationHooks,
FunctionReference[] memory preRuntimeValidationHooks
)
{
preUserOpValidationHooks =
toFunctionReferenceArray(getAccountStorage().selectorData[selector].preUserOpValidationHooks);
preRuntimeValidationHooks =
toFunctionReferenceArray(getAccountStorage().selectorData[selector].preRuntimeValidationHooks);
}

/// @inheritdoc IAccountLoupe
function getInstalledPlugins() external view returns (address[] memory pluginAddresses) {
pluginAddresses = getAccountStorage().plugins.values();
}

function _getHooks(HookGroup storage hooks) internal view returns (ExecutionHooks[] memory execHooks) {
uint256 preExecHooksLength = hooks.preHooks.length();
uint256 postOnlyExecHooksLength = hooks.postOnlyHooks.length();
uint256 maxExecHooksLength = postOnlyExecHooksLength;

// There can only be as many associated post hooks to run as there are pre hooks.
for (uint256 i = 0; i < preExecHooksLength;) {
(, uint256 count) = hooks.preHooks.at(i);
unchecked {
maxExecHooksLength += (count + 1);
++i;
}
}

// Overallocate on length - not all of this may get filled up. We set the correct length later.
execHooks = new ExecutionHooks[](maxExecHooksLength);
uint256 actualExecHooksLength;

for (uint256 i = 0; i < preExecHooksLength;) {
(bytes32 key,) = hooks.preHooks.at(i);
FunctionReference preExecHook = FunctionReference.wrap(bytes21(key));

uint256 associatedPostExecHooksLength = hooks.associatedPostHooks[preExecHook].length();
if (associatedPostExecHooksLength > 0) {
for (uint256 j = 0; j < associatedPostExecHooksLength;) {
execHooks[actualExecHooksLength].preExecHook = preExecHook;
(key,) = hooks.associatedPostHooks[preExecHook].at(j);
execHooks[actualExecHooksLength].postExecHook = FunctionReference.wrap(bytes21(key));

unchecked {
++actualExecHooksLength;
++j;
}
}
} else {
execHooks[actualExecHooksLength].preExecHook = preExecHook;

unchecked {
++actualExecHooksLength;
}
}

unchecked {
++i;
}
}

for (uint256 i = 0; i < postOnlyExecHooksLength;) {
(bytes32 key,) = hooks.postOnlyHooks.at(i);
execHooks[actualExecHooksLength].postExecHook = FunctionReference.wrap(bytes21(key));

unchecked {
++actualExecHooksLength;
++i;
}
}

// Trim the exec hooks array to the actual length, since we may have overallocated.
assembly ("memory-safe") {
mstore(execHooks, actualExecHooksLength)
}
}
}
1 change: 1 addition & 0 deletions src/account/AccountStorageInitializable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.19;

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

import {AccountStorage, getAccountStorage} from "../libraries/AccountStorage.sol";

abstract contract AccountStorageInitializable {
Expand Down
Loading