-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow direct plugin calls with validation & permission hooks (#90)
Showing
9 changed files
with
315 additions
and
93 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,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 }] | ||
} | ||
} |
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
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
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
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
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,133 @@ | ||
pragma solidity ^0.8.19; | ||
|
||
import {DirectCallPlugin} from "../mocks/plugins/DirectCallPlugin.sol"; | ||
import {ExecutionHook} from "../../src/interfaces/IAccountLoupe.sol"; | ||
import {IStandardExecutor, Call} from "../../src/interfaces/IStandardExecutor.sol"; | ||
import {PluginEntityLib, PluginEntity} from "../../src/helpers/PluginEntityLib.sol"; | ||
import {ValidationConfig, ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; | ||
import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; | ||
|
||
import {AccountTestBase} from "../utils/AccountTestBase.sol"; | ||
|
||
contract DirectCallsFromPluginTest is AccountTestBase { | ||
using ValidationConfigLib for ValidationConfig; | ||
|
||
DirectCallPlugin internal _plugin; | ||
PluginEntity internal _pluginEntity; | ||
|
||
function setUp() public { | ||
_plugin = new DirectCallPlugin(); | ||
assertFalse(_plugin.preHookRan()); | ||
assertFalse(_plugin.postHookRan()); | ||
_pluginEntity = PluginEntityLib.pack(address(_plugin), type(uint32).max); | ||
} | ||
|
||
/* -------------------------------------------------------------------------- */ | ||
/* Negatives */ | ||
/* -------------------------------------------------------------------------- */ | ||
|
||
function test_Fail_DirectCallPluginNotInstalled() external { | ||
vm.prank(address(_plugin)); | ||
vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); | ||
account1.execute(address(0), 0, ""); | ||
} | ||
|
||
function test_Fail_DirectCallPluginUninstalled() external { | ||
_installPlugin(); | ||
|
||
_uninstallPlugin(); | ||
|
||
vm.prank(address(_plugin)); | ||
vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); | ||
account1.execute(address(0), 0, ""); | ||
} | ||
|
||
function test_Fail_DirectCallPluginCallOtherSelector() external { | ||
_installPlugin(); | ||
|
||
Call[] memory calls = new Call[](0); | ||
|
||
vm.prank(address(_plugin)); | ||
vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.executeBatch.selector)); | ||
account1.executeBatch(calls); | ||
} | ||
|
||
/* -------------------------------------------------------------------------- */ | ||
/* Positives */ | ||
/* -------------------------------------------------------------------------- */ | ||
|
||
function test_Pass_DirectCallFromPluginPrank() external { | ||
_installPlugin(); | ||
|
||
vm.prank(address(_plugin)); | ||
account1.execute(address(0), 0, ""); | ||
|
||
assertTrue(_plugin.preHookRan()); | ||
assertTrue(_plugin.postHookRan()); | ||
} | ||
|
||
function test_Pass_DirectCallFromPluginCallback() external { | ||
_installPlugin(); | ||
|
||
bytes memory encodedCall = abi.encodeCall(DirectCallPlugin.directCall, ()); | ||
|
||
vm.prank(address(entryPoint)); | ||
bytes memory result = account1.execute(address(_plugin), 0, encodedCall); | ||
|
||
assertTrue(_plugin.preHookRan()); | ||
assertTrue(_plugin.postHookRan()); | ||
|
||
// the directCall() function in the _plugin calls back into `execute()` with an encoded call back into the | ||
// _plugin's getData() function. | ||
assertEq(abi.decode(result, (bytes)), abi.encode(_plugin.getData())); | ||
} | ||
|
||
function test_Flow_DirectCallFromPluginSequence() external { | ||
// Install => Succeesfully call => uninstall => fail to call | ||
|
||
_installPlugin(); | ||
|
||
vm.prank(address(_plugin)); | ||
account1.execute(address(0), 0, ""); | ||
|
||
assertTrue(_plugin.preHookRan()); | ||
assertTrue(_plugin.postHookRan()); | ||
|
||
_uninstallPlugin(); | ||
|
||
vm.prank(address(_plugin)); | ||
vm.expectRevert(_buildDirectCallDisallowedError(IStandardExecutor.execute.selector)); | ||
account1.execute(address(0), 0, ""); | ||
} | ||
|
||
/* -------------------------------------------------------------------------- */ | ||
/* Internals */ | ||
/* -------------------------------------------------------------------------- */ | ||
|
||
function _installPlugin() internal { | ||
bytes4[] memory selectors = new bytes4[](1); | ||
selectors[0] = IStandardExecutor.execute.selector; | ||
|
||
ExecutionHook[] memory permissionHooks = new ExecutionHook[](1); | ||
bytes[] memory permissionHookInitDatas = new bytes[](1); | ||
|
||
permissionHooks[0] = ExecutionHook({hookFunction: _pluginEntity, isPreHook: true, isPostHook: true}); | ||
|
||
bytes memory encodedPermissionHooks = abi.encode(permissionHooks, permissionHookInitDatas); | ||
|
||
vm.prank(address(entryPoint)); | ||
|
||
ValidationConfig validationConfig = ValidationConfigLib.pack(_pluginEntity, false, false); | ||
|
||
account1.installValidation(validationConfig, selectors, "", "", encodedPermissionHooks); | ||
} | ||
|
||
function _uninstallPlugin() internal { | ||
vm.prank(address(entryPoint)); | ||
account1.uninstallValidation(_pluginEntity, "", abi.encode(new bytes[](0)), abi.encode(new bytes[](1))); | ||
} | ||
|
||
function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { | ||
return abi.encodeWithSelector(UpgradeableModularAccount.ValidationFunctionMissing.selector, selector); | ||
} | ||
} |
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
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
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,47 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.19; | ||
|
||
import {PluginManifest, PluginMetadata} from "../../../src/interfaces/IPlugin.sol"; | ||
import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol"; | ||
import {IExecutionHook} from "../../../src/interfaces/IExecutionHook.sol"; | ||
|
||
import {BasePlugin} from "../../../src/plugins/BasePlugin.sol"; | ||
|
||
contract DirectCallPlugin is BasePlugin, IExecutionHook { | ||
bool public preHookRan = false; | ||
bool public postHookRan = false; | ||
|
||
function onInstall(bytes calldata) external override {} | ||
|
||
function onUninstall(bytes calldata) external override {} | ||
|
||
function pluginManifest() external pure override returns (PluginManifest memory) {} | ||
|
||
function directCall() external returns (bytes memory) { | ||
return IStandardExecutor(msg.sender).execute(address(this), 0, abi.encodeCall(this.getData, ())); | ||
} | ||
|
||
function getData() external pure returns (bytes memory) { | ||
return hex"04546b"; | ||
} | ||
|
||
function pluginMetadata() external pure override returns (PluginMetadata memory) {} | ||
|
||
function preExecutionHook(uint32, address sender, uint256, bytes calldata) | ||
external | ||
override | ||
returns (bytes memory) | ||
{ | ||
require(sender == address(this), "mock direct call pre permission hook failed"); | ||
preHookRan = true; | ||
return abi.encode(keccak256(hex"04546b")); | ||
} | ||
|
||
function postExecutionHook(uint32, bytes calldata preExecHookData) external override { | ||
require( | ||
abi.decode(preExecHookData, (bytes32)) == keccak256(hex"04546b"), | ||
"mock direct call post permission hook failed" | ||
); | ||
postHookRan = true; | ||
} | ||
} |