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

feat: add replacePlugin #38

Closed
wants to merge 58 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d1d1797
Add replacePlugin scratch
sm-stack Jan 17, 2024
468578a
Add replacePlugin scratch
sm-stack Jan 20, 2024
80fce06
Merge branch 'erc6900:main' into replace-plugin-0.7
sm-stack Jan 24, 2024
085bfd3
Merge branch 'erc6900:main' into replace-plugin-0.7
sm-stack Jan 27, 2024
332e287
Remove enableExecFromPlugin
sm-stack Jan 27, 2024
731fa9c
Apply forge fmt
sm-stack Jan 27, 2024
055d715
Update replacePlugin interface and remove dependency checks
sm-stack Jan 27, 2024
252b032
Add a simple test for replacePlugin
sm-stack Jan 27, 2024
112c30a
Feat.add replacePlugin interface
brynnPark Jan 28, 2024
1f85139
Feat. add migration Structure
brynnPark Jan 28, 2024
63960f3
:sparkles: Feat. add dat migration logic
brynnPark Jan 28, 2024
45e51ba
feat: migration logic added
brynnPark Jan 28, 2024
18e6d25
Remove unnecessary parts in updating permitted calls
sm-stack Jan 28, 2024
d35c373
Add basic test for replacePlugin on SingleOwnerPlugin
sm-stack Jan 28, 2024
4daca10
feat: migration implementation
brynnPark Jan 28, 2024
58193c9
:art: Feat: data migration done
brynnPark Jan 28, 2024
5c1b20f
Update for valid test for replacePlugin
sm-stack Jan 29, 2024
7612045
Comment: Add comments in format
brynnPark Jan 29, 2024
fdc0830
Improve: onReplace functions
brynnPark Jan 30, 2024
d552877
Improve: onReplace functions
brynnPark Jan 30, 2024
15aa6fa
Improve: onReplace functions
brynnPark Jan 30, 2024
1020993
Implement VersionRegistry
Sangyeup Jan 30, 2024
0e97f05
Modify plugin implementations to handle VersionRegistry
Sangyeup Jan 30, 2024
d785fe9
Implement version verification for replacePlugin
Sangyeup Jan 30, 2024
1366184
Modify test codes to handle VersionRegistry
Sangyeup Jan 30, 2024
0b93117
Merge branch 'replace-plugin-0.7-brynn' of https://github.com/deciphe…
brynnPark Jan 31, 2024
8602bb0
Style: change the sequence of onReplace functions
brynnPark Jan 31, 2024
359f1da
Merge origin/replace-plugin-0.7 into origin/replace-plugin-0.7-sang
sm-stack Feb 3, 2024
830a30e
Update VersionRegistry spec
sm-stack Feb 3, 2024
7e6a177
Remove unnecessary codes and apply formatting
sm-stack Feb 4, 2024
d0a2b0f
sytle: PluginReplaced event
brynnPark Feb 4, 2024
9017e6e
Rename: getDataForReplcement function
brynnPark Feb 4, 2024
b7d7119
modify: require statement to if -revert
Sangyeup Feb 5, 2024
c281502
remove: getVersionRegistry function in plugin
Sangyeup Feb 5, 2024
7e99b75
add isPluginCompatible function
Sangyeup Feb 5, 2024
0ce153e
Add error for non-digit character
sm-stack Feb 10, 2024
bc8b5fa
Fix data structures and interfaces in VersionRegistry
sm-stack Feb 10, 2024
d84e2e0
Fix tests for updated version registry and replacePlugin
sm-stack Feb 10, 2024
e682e3d
Add mock SimpleOwnerPlugin with different version info
sm-stack Feb 10, 2024
19adaed
Apply formatting
sm-stack Feb 10, 2024
d3d0d77
Replace address array with EnumerableSet
sm-stack Feb 11, 2024
dbbbbc2
Merge pull request #3 from decipherhub/replace-plugin-0.7-brynn
brynnPark Feb 11, 2024
819941f
Merge branch 'replace-plugin-0.7' into replace-plugin-0.7-sang
sm-stack Feb 11, 2024
4cc7b7e
Merge pull request #4 from decipherhub/replace-plugin-0.7-sang
sm-stack Feb 11, 2024
9d01063
Remove unnecessary parts in updating permitted calls
sm-stack Jan 28, 2024
3752eb4
Add basic test for replacePlugin on SingleOwnerPlugin
sm-stack Jan 28, 2024
1fec533
Merge branch 'replace-plugin-0.7-sm' of https://github.com/decipherhu…
sm-stack Feb 11, 2024
ead651d
Correct function name
sm-stack Feb 11, 2024
9aa652c
Remove duplicates on the test code
sm-stack Feb 11, 2024
26af0f7
Move positions of functions at SingleOwnerPlugin
sm-stack Feb 11, 2024
a3cb129
Move VersionRegistry into a separate folder
sm-stack Feb 11, 2024
0cb9223
Change visibility of getPluginVersion
sm-stack Feb 11, 2024
063d4b9
Add test for replacePLugin
sm-stack Feb 13, 2024
3604d95
Apply formatting
sm-stack Feb 13, 2024
b733e40
Add test checking functionality diffs between replacement
sm-stack Feb 13, 2024
8161473
Merge pull request #2 from decipherhub/replace-plugin-0.7-sm
sm-stack Feb 13, 2024
45ab4bc
Revert committed changes in OptimizedTest
sm-stack Feb 13, 2024
b762d11
Remove unused constructor at ResultConsumerPlugin
sm-stack Feb 13, 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
312 changes: 312 additions & 0 deletions src/account/PluginManagerInternals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
PluginManifest
} from "../interfaces/IPlugin.sol";
import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol";
import {IVersionRegistry} from "../interfaces/IVersionRegistry.sol";
import {
AccountStorage,
getAccountStorage,
Expand Down Expand Up @@ -44,6 +45,8 @@ abstract contract PluginManagerInternals is IPluginManager {
error PluginNotInstalled(address plugin);
error RuntimeValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction);
error UserOpValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction);
error IncompatiblePluginVersion(address oldPlugin, address newPlugin);
error onReplaceForNewPluginFailed(address plugin, bytes revertReason);

modifier notNullFunction(FunctionReference functionReference) {
if (functionReference.isEmpty()) {
Expand Down Expand Up @@ -643,6 +646,315 @@ abstract contract PluginManagerInternals is IPluginManager {
emit PluginUninstalled(plugin, onUninstallSuccess);
}

function _replacePlugin(address oldPlugin, address newPlugin, bytes32 newManifestHash) internal {
AccountStorage storage _storage = getAccountStorage();
PluginManifest memory oldManifest = IPlugin(oldPlugin).pluginManifest();

// To avoid Stack Too Deep
{
IVersionRegistry versionRegistry = IVersionRegistry(oldManifest.versionRegistry);

// Check if new plugin is a compatible patch-level upgrade of an old plugin.
if (!versionRegistry.isPluginCompatible(oldPlugin, newPlugin)) {
revert IncompatiblePluginVersion(oldPlugin, newPlugin);
}
}

// Check if the old plugin exists.
if (!_storage.plugins.remove(oldPlugin)) {
revert PluginNotInstalled(oldPlugin);
}

// Check if the plugin exists.
if (!_storage.plugins.add(newPlugin)) {
revert PluginAlreadyInstalled(newPlugin);
}

// Check manifest hash.
PluginManifest memory newManifest = IPlugin(newPlugin).pluginManifest();
if (!_isValidPluginManifest(newManifest, newManifestHash)) {
revert InvalidPluginManifest();
}

// Check that the dependencies match the manifest.
FunctionReference[] memory dependencies = _storage.pluginData[oldPlugin].dependencies;
if (dependencies.length != newManifest.dependencyInterfaceIds.length) {
revert InvalidDependenciesProvided();
}

// Replace the plugin metadata to the account
_storage.pluginData[oldPlugin].manifestHash = bytes32(0);
_storage.pluginData[oldPlugin].dependencies = new FunctionReference[](0);
_storage.pluginData[newPlugin].manifestHash = newManifestHash;
_storage.pluginData[newPlugin].dependencies = dependencies;

// Update components according to the manifest.
// All conflicts should revert.

// Mark whether or not this plugin may spend native token amounts
if (newManifest.canSpendNativeToken) {
_storage.pluginData[newPlugin].canSpendNativeToken = true;
}

uint256 length = newManifest.executionFunctions.length;
for (uint256 i = 0; i < length;) {
if (i < oldManifest.executionFunctions.length) {
_removeExecutionFunction(oldManifest.executionFunctions[i]);
}
_setExecutionFunction(newManifest.executionFunctions[i], newPlugin);

unchecked {
++i;
}
}

length = newManifest.permittedExecutionSelectors.length;
for (uint256 i = 0; i < length;) {
if (i < oldManifest.permittedExecutionSelectors.length) {
_storage.callPermitted[getPermittedCallKey(oldPlugin, oldManifest.permittedExecutionSelectors[i])]
= false;
}
_storage.callPermitted[getPermittedCallKey(newPlugin, newManifest.permittedExecutionSelectors[i])] =
true;

unchecked {
++i;
}
}

// Update the permitted external calls of the account.
if (newManifest.permitAnyExternalAddress) {
_storage.pluginData[oldPlugin].anyExternalExecPermitted = false;
_storage.pluginData[newPlugin].anyExternalExecPermitted = true;
} else {
// Only update the specific permitted external calls if "permit any" flag was not set.
length = newManifest.permittedExternalCalls.length;
for (uint256 i = 0; i < length;) {
ManifestExternalCallPermission memory externalCallPermission =
newManifest.permittedExternalCalls[i];
PermittedExternalCallData storage newPermittedExternalCallData =
_storage.permittedExternalCalls[IPlugin(newPlugin)][externalCallPermission.externalAddress];

newPermittedExternalCallData.addressPermitted = true;

if (i < oldManifest.permittedExternalCalls.length) {
PermittedExternalCallData storage oldPermittedExternalCallData =
_storage.permittedExternalCalls[IPlugin(oldPlugin)][externalCallPermission.externalAddress];

oldPermittedExternalCallData.addressPermitted = false;

if (externalCallPermission.permitAnySelector) {
oldPermittedExternalCallData.anySelectorPermitted = false;
newPermittedExternalCallData.anySelectorPermitted = true;
} else {
uint256 oldExternalContractSelectorsLength = externalCallPermission.selectors.length;
for (uint256 j = 0; j < oldExternalContractSelectorsLength;) {
oldPermittedExternalCallData.permittedSelectors[externalCallPermission.selectors[j]] =
false;
newPermittedExternalCallData.permittedSelectors[externalCallPermission.selectors[j]] =
true;

unchecked {
++j;
}
}
}
} else {
if (externalCallPermission.permitAnySelector) {
newPermittedExternalCallData.anySelectorPermitted = true;
} else {
uint256 newExternalContractSelectorsLength = externalCallPermission.selectors.length;
for (uint256 j = 0; j < newExternalContractSelectorsLength;) {
newPermittedExternalCallData.permittedSelectors[externalCallPermission.selectors[j]] =
true;

unchecked {
++j;
}
}
}
}

unchecked {
++i;
}
}
}

length = newManifest.userOpValidationFunctions.length;
for (uint256 i = 0; i < length;) {
ManifestAssociatedFunction memory mv = newManifest.userOpValidationFunctions[i];
if (i < oldManifest.userOpValidationFunctions.length) {
_removeUserOpValidationFunction(
mv.executionSelector,
_resolveManifestFunction(
mv.associatedFunction, oldPlugin, dependencies, ManifestAssociatedFunctionType.NONE
)
);
}

_addUserOpValidationFunction(
mv.executionSelector,
_resolveManifestFunction(
mv.associatedFunction, newPlugin, dependencies, ManifestAssociatedFunctionType.NONE
)
);

unchecked {
++i;
}
}

length = newManifest.runtimeValidationFunctions.length;
for (uint256 i = 0; i < length;) {
ManifestAssociatedFunction memory mv = newManifest.runtimeValidationFunctions[i];
if (i < oldManifest.runtimeValidationFunctions.length) {
_removeRuntimeValidationFunction(
mv.executionSelector,
_resolveManifestFunction(
mv.associatedFunction,
oldPlugin,
dependencies,
ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW
)
);
}

_addRuntimeValidationFunction(
mv.executionSelector,
_resolveManifestFunction(
mv.associatedFunction,
newPlugin,
dependencies,
ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW
)
);

unchecked {
++i;
}
}

// Hooks are not allowed to be provided as dependencies, so we use an empty array for resolving them.
FunctionReference[] memory emptyDependencies;

length = newManifest.preUserOpValidationHooks.length;
for (uint256 i = 0; i < length;) {
ManifestAssociatedFunction memory mh = newManifest.preUserOpValidationHooks[i];
if (i < oldManifest.preUserOpValidationHooks.length) {
_removePreUserOpValidationHook(
mh.executionSelector,
_resolveManifestFunction(
mh.associatedFunction,
oldPlugin,
emptyDependencies,
ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY
)
);
}

_addPreUserOpValidationHook(
mh.executionSelector,
_resolveManifestFunction(
mh.associatedFunction,
newPlugin,
emptyDependencies,
ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY
)
);

unchecked {
++i;
}
}

length = newManifest.preRuntimeValidationHooks.length;
for (uint256 i = 0; i < length;) {
ManifestAssociatedFunction memory mh = newManifest.preRuntimeValidationHooks[i];
if (i < oldManifest.preRuntimeValidationHooks.length) {
_removePreRuntimeValidationHook(
mh.executionSelector,
_resolveManifestFunction(
mh.associatedFunction,
oldPlugin,
emptyDependencies,
ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY
)
);
}

_addPreRuntimeValidationHook(
mh.executionSelector,
_resolveManifestFunction(
mh.associatedFunction,
newPlugin,
emptyDependencies,
ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY
)
);

unchecked {
++i;
}
}

length = newManifest.executionHooks.length;
for (uint256 i = 0; i < length;) {
ManifestExecutionHook memory mh = newManifest.executionHooks[i];
if (i < oldManifest.executionHooks.length) {
_removeExecHooks(
mh.executionSelector,
_resolveManifestFunction(
mh.preExecHook,
oldPlugin,
emptyDependencies,
ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY
),
_resolveManifestFunction(
mh.postExecHook, oldPlugin, emptyDependencies, ManifestAssociatedFunctionType.NONE
)
);
}

_addExecHooks(
mh.executionSelector,
_resolveManifestFunction(
mh.preExecHook,
newPlugin,
emptyDependencies,
ManifestAssociatedFunctionType.PRE_HOOK_ALWAYS_DENY
),
_resolveManifestFunction(
mh.postExecHook, newPlugin, emptyDependencies, ManifestAssociatedFunctionType.NONE
)
);

unchecked {
++i;
}
}

delete _storage.pluginData[oldPlugin];

// Retrieve data from the old plugin for migration
bytes memory migrationData = IPlugin(oldPlugin).getDataForReplacement();

// Call the old plugin's clean-up function
bool onReplaceOldSuccess = true;
try IPlugin(oldPlugin).onReplaceForOldPlugin() {}
catch {
onReplaceOldSuccess = false;
}

// Pass the migration data to the new plugin
try IPlugin(newPlugin).onReplaceForNewPlugin(migrationData) {}
catch (bytes memory revertReason) {
revert onReplaceForNewPluginFailed(newPlugin, revertReason);
}

emit PluginReplaced(oldPlugin, newPlugin, onReplaceOldSuccess);
}

function _addOrIncrement(EnumerableMap.Bytes32ToUintMap storage map, bytes32 key) internal {
(bool success, uint256 value) = map.tryGet(key);
map.set(key, success ? value + 1 : 0);
Expand Down
9 changes: 9 additions & 0 deletions src/account/UpgradeableModularAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,15 @@ contract UpgradeableModularAccount is
_uninstallPlugin(plugin, manifest, pluginUninstallData);
}

/// @inheritdoc IPluginManager
function replacePlugin(address oldPlugin, address newPlugin, bytes32 newManifestHash)
external
override
wrapNativeFunction
{
_replacePlugin(oldPlugin, newPlugin, newManifestHash);
}

/// @notice ERC165 introspection
/// @dev returns true for `IERC165.interfaceId` and false for `0xFFFFFFFF`
/// @param interfaceId interface id to check against
Expand Down
50 changes: 50 additions & 0 deletions src/helpers/VersionDecoder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

struct Version {
uint256 major;
uint256 minor;
uint256 patch;
}

error NonDigitChar();

function decodeVersion(string memory versionString) pure returns (Version memory) {
uint256 major;
uint256 minor;
uint256 patch;
uint256 lastIndex = 0;
uint256 dotCount = 0;

// Convert the versionString to bytes for manipulation
bytes memory versionBytes = bytes(versionString);

for (uint256 i = 0; i < versionBytes.length; i++) {
if (versionBytes[i] == 0x2E) {
if (dotCount == 0) {
major = _parseUint256(versionBytes, lastIndex, i);
} else if (dotCount == 1) {
minor = _parseUint256(versionBytes, lastIndex, i);
}
lastIndex = i + 1;
dotCount++;
}
}

// Parse the patch version, which is after the last dot
patch = _parseUint256(versionBytes, lastIndex, versionBytes.length);

return Version(major, minor, patch);
}

function _parseUint256(bytes memory b, uint256 start, uint256 end) pure returns (uint256) {
uint256 result = 0;
for (uint256 i = start; i < end; i++) {
// Ensure the character is a digit
if (b[i] < 0x30 || b[i] > 0x39) {
revert NonDigitChar();
}
result = result * 10 + (uint256(uint8(b[i])) - 0x30);
}
return result;
}
Loading