Skip to content

Commit

Permalink
chore: update to 4337 v0.7 (2/n) (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
howydev authored May 1, 2024
1 parent edf1b54 commit 145acbb
Show file tree
Hide file tree
Showing 13 changed files with 129 additions and 168 deletions.
1 change: 0 additions & 1 deletion src/account/AccountLoupe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ abstract contract AccountLoupe is IAccountLoupe {

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
Expand Down
72 changes: 47 additions & 25 deletions src/account/AccountStorageInitializable.sol
Original file line number Diff line number Diff line change
@@ -1,45 +1,67 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.25;

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

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

/// @title AccountStorageInitializable
/// @dev Bulk of the impl is lifted from OZ 5.0 Initializible
abstract contract AccountStorageInitializable {
error AlreadyInitialized();
error AlreadyInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);

/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();

/**
* @dev The contract is not initializing.
*/
error NotInitializing();

/// @notice Modifier to put on function intended to be called only once per implementation
/// @dev Reverts if the contract has already been initialized
modifier initializer() {
AccountStorage storage _storage = getAccountStorage();
bool isTopLevelCall = !_storage.initializing;
if (
isTopLevelCall && _storage.initialized < 1
|| !Address.isContract(address(this)) && _storage.initialized == 1
) {
_storage.initialized = 1;
if (isTopLevelCall) {
_storage.initializing = true;
}
_;
if (isTopLevelCall) {
_storage.initializing = false;
}
} else {
revert AlreadyInitialized();
AccountStorage storage $ = getAccountStorage();

// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$.initializing;
uint64 initialized = $.initialized;

// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;

if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$.initialized = 1;
if (isTopLevelCall) {
$.initializing = true;
}
_;
if (isTopLevelCall) {
$.initializing = false;
emit Initialized(1);
}
}

/// @notice Internal function to disable calls to initialization functions
/// @dev Reverts if the contract has already been initialized
function _disableInitializers() internal virtual {
AccountStorage storage _storage = getAccountStorage();
if (_storage.initializing) {
revert AlreadyInitializing();
AccountStorage storage $ = getAccountStorage();
if ($.initializing) {
revert InvalidInitialization();
}
if (_storage.initialized != type(uint8).max) {
_storage.initialized = type(uint8).max;
if ($.initialized != type(uint8).max) {
$.initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
}
7 changes: 1 addition & 6 deletions src/account/UpgradeableModularAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,6 @@ contract UpgradeableModularAccount is
return getAccountStorage().supportedIfaces[interfaceId] > 0;
}

/// @inheritdoc UUPSUpgradeable
function upgradeTo(address newImplementation) public override onlyProxy wrapNativeFunction {
_upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
}

/// @inheritdoc UUPSUpgradeable
function upgradeToAndCall(address newImplementation, bytes memory data)
public
Expand All @@ -305,7 +300,7 @@ contract UpgradeableModularAccount is
onlyProxy
wrapNativeFunction
{
_upgradeToAndCallUUPS(newImplementation, data, true);
super.upgradeToAndCall(newImplementation, data);
}

/// @notice Gets the entry point for this account
Expand Down
34 changes: 10 additions & 24 deletions src/plugins/TokenReceiverPlugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity ^0.8.25;

import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {IERC777Recipient} from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol";
import {IERC1155Receiver} from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol";

import {
Expand All @@ -18,7 +17,7 @@ import {BasePlugin} from "./BasePlugin.sol";
/// @author ERC-6900 Authors
/// @notice This plugin allows modular accounts to receive various types of tokens by implementing
/// required token receiver interfaces.
contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC777Recipient, IERC1155Receiver {
contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC1155Receiver {
string public constant NAME = "Token Receiver Plugin";
string public constant VERSION = "1.0.0";
string public constant AUTHOR = "ERC-6900 Authors";
Expand All @@ -27,13 +26,6 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC777Recipient, I
// ┃ Execution functions ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

function tokensReceived(address, address, address, uint256, bytes calldata, bytes calldata)
external
pure
override
// solhint-disable-next-line no-empty-blocks
{}

function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
Expand Down Expand Up @@ -72,40 +64,34 @@ contract TokenReceiverPlugin is BasePlugin, IERC721Receiver, IERC777Recipient, I
function pluginManifest() external pure override returns (PluginManifest memory) {
PluginManifest memory manifest;

manifest.executionFunctions = new bytes4[](4);
manifest.executionFunctions[0] = this.tokensReceived.selector;
manifest.executionFunctions[1] = this.onERC721Received.selector;
manifest.executionFunctions[2] = this.onERC1155Received.selector;
manifest.executionFunctions[3] = this.onERC1155BatchReceived.selector;
manifest.executionFunctions = new bytes4[](3);
manifest.executionFunctions[0] = this.onERC721Received.selector;
manifest.executionFunctions[1] = this.onERC1155Received.selector;
manifest.executionFunctions[2] = this.onERC1155BatchReceived.selector;

// Only runtime validationFunction is needed since callbacks come from token contracts only
ManifestFunction memory alwaysAllowRuntime = ManifestFunction({
functionType: ManifestAssociatedFunctionType.RUNTIME_VALIDATION_ALWAYS_ALLOW,
functionId: 0, // Unused.
dependencyIndex: 0 // Unused.
});
manifest.validationFunctions = new ManifestAssociatedFunction[](4);
manifest.validationFunctions = new ManifestAssociatedFunction[](3);
manifest.validationFunctions[0] = ManifestAssociatedFunction({
executionSelector: this.tokensReceived.selector,
associatedFunction: alwaysAllowRuntime
});
manifest.validationFunctions[1] = ManifestAssociatedFunction({
executionSelector: this.onERC721Received.selector,
associatedFunction: alwaysAllowRuntime
});
manifest.validationFunctions[2] = ManifestAssociatedFunction({
manifest.validationFunctions[1] = ManifestAssociatedFunction({
executionSelector: this.onERC1155Received.selector,
associatedFunction: alwaysAllowRuntime
});
manifest.validationFunctions[3] = ManifestAssociatedFunction({
manifest.validationFunctions[2] = ManifestAssociatedFunction({
executionSelector: this.onERC1155BatchReceived.selector,
associatedFunction: alwaysAllowRuntime
});

manifest.interfaceIds = new bytes4[](3);
manifest.interfaceIds = new bytes4[](2);
manifest.interfaceIds[0] = type(IERC721Receiver).interfaceId;
manifest.interfaceIds[1] = type(IERC777Recipient).interfaceId;
manifest.interfaceIds[2] = type(IERC1155Receiver).interfaceId;
manifest.interfaceIds[1] = type(IERC1155Receiver).interfaceId;

return manifest;
}
Expand Down
12 changes: 5 additions & 7 deletions src/plugins/owner/SingleOwnerPlugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {IPluginManager} from "../../interfaces/IPluginManager.sol";
Expand Down Expand Up @@ -38,6 +39,7 @@ import {ISingleOwnerPlugin} from "./ISingleOwnerPlugin.sol";
/// send user operations through a bundler.
contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 {
using ECDSA for bytes32;
using MessageHashUtils for bytes32;

string public constant NAME = "Single Owner Plugin";
string public constant VERSION = "1.0.0";
Expand Down Expand Up @@ -99,7 +101,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 {
{
if (functionId == uint8(FunctionId.VALIDATION_OWNER_OR_SELF)) {
// Validate the user op signature against the owner.
(address signer,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature);
(address signer,,) = (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature);
if (signer == address(0) || signer != _owners[msg.sender]) {
return _SIG_VALIDATION_FAILED;
}
Expand Down Expand Up @@ -154,7 +156,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 {
functionId: uint8(FunctionId.VALIDATION_OWNER_OR_SELF),
dependencyIndex: 0 // Unused.
});
manifest.validationFunctions = new ManifestAssociatedFunction[](8);
manifest.validationFunctions = new ManifestAssociatedFunction[](7);
manifest.validationFunctions[0] = ManifestAssociatedFunction({
executionSelector: this.transferOwnership.selector,
associatedFunction: ownerValidationFunction
Expand All @@ -176,10 +178,6 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 {
associatedFunction: ownerValidationFunction
});
manifest.validationFunctions[5] = ManifestAssociatedFunction({
executionSelector: UUPSUpgradeable.upgradeTo.selector,
associatedFunction: ownerValidationFunction
});
manifest.validationFunctions[6] = ManifestAssociatedFunction({
executionSelector: UUPSUpgradeable.upgradeToAndCall.selector,
associatedFunction: ownerValidationFunction
});
Expand All @@ -189,7 +187,7 @@ contract SingleOwnerPlugin is BasePlugin, ISingleOwnerPlugin, IERC1271 {
functionId: 0, // Unused.
dependencyIndex: 0 // Unused.
});
manifest.validationFunctions[7] = ManifestAssociatedFunction({
manifest.validationFunctions[6] = ManifestAssociatedFunction({
executionSelector: this.isValidSignature.selector,
associatedFunction: alwaysAllowRuntime
});
Expand Down
16 changes: 16 additions & 0 deletions test/account/UpgradeableModularAccount.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {console} from "forge-std/Test.sol";

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";

import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol";
import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol";
Expand All @@ -23,6 +24,7 @@ import {AccountTestBase} from "../utils/AccountTestBase.sol";

contract UpgradeableModularAccountTest is AccountTestBase {
using ECDSA for bytes32;
using MessageHashUtils for bytes32;

TokenReceiverPlugin public tokenReceiverPlugin;

Expand Down Expand Up @@ -403,6 +405,20 @@ contract UpgradeableModularAccountTest is AccountTestBase {
vm.stopPrank();
}

function test_upgradeToAndCall() public {
vm.startPrank(owner1);
UpgradeableModularAccount account3 = new UpgradeableModularAccount(entryPoint);
bytes32 slot = account3.proxiableUUID();

// account has impl from factory
assertEq(
address(factory.accountImplementation()), address(uint160(uint256(vm.load(address(account1), slot))))
);
account1.upgradeToAndCall(address(account3), bytes(""));
// account has new impl
assertEq(address(account3), address(uint160(uint256(vm.load(address(account1), slot)))));
}

// Internal Functions

function _printStorageReadsAndWrites(address addr) internal {
Expand Down
4 changes: 3 additions & 1 deletion test/comparison/CompareSimpleAccount.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.19;
import {Test} from "forge-std/Test.sol";

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol";
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";

Expand All @@ -14,6 +15,7 @@ import {Counter} from "../mocks/Counter.sol";

contract CompareSimpleAccountTest is Test {
using ECDSA for bytes32;
using MessageHashUtils for bytes32;

EntryPoint public entryPoint;
address payable public beneficiary;
Expand All @@ -37,7 +39,7 @@ contract CompareSimpleAccountTest is Test {

// helper function to compress 2 gas values into a single bytes32
function _encodeGas(uint256 g1, uint256 g2) internal pure returns (bytes32) {
return bytes32(uint256(g1 << 128 + uint128(g2)));
return bytes32(uint256((g1 << 128) + uint128(g2)));
}

function setUp() public {
Expand Down
2 changes: 1 addition & 1 deletion test/libraries/AccountStorage.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ contract AccountStorageTest is Test {
assertEq(uint256(vm.load(address(impl), _ACCOUNT_STORAGE_SLOT)), type(uint8).max);

// should revert if we try to initialize again
vm.expectRevert(AccountStorageInitializable.AlreadyInitialized.selector);
vm.expectRevert(AccountStorageInitializable.InvalidInitialization.selector);
impl.initialize();
}

Expand Down
12 changes: 12 additions & 0 deletions test/mocks/MockERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MockERC721 is ERC721 {
constructor(string memory name, string memory symbol) ERC721(name, symbol) {}

function mint(address account, uint256 tokenId) external {
_mint(account, tokenId);
}
}
51 changes: 0 additions & 51 deletions test/mocks/MockERC777.sol

This file was deleted.

Loading

0 comments on commit 145acbb

Please sign in to comment.