diff --git a/contracts/account/Account.sol b/contracts/account/Account.sol index a14018a..e747e8b 100644 --- a/contracts/account/Account.sol +++ b/contracts/account/Account.sol @@ -7,7 +7,6 @@ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import {ERC7739} from "../utils/cryptography/ERC7739.sol"; -import {ERC7821} from "./extensions/ERC7821.sol"; import {AccountCore} from "./AccountCore.sol"; /** @@ -17,10 +16,12 @@ import {AccountCore} from "./AccountCore.sol"; * * {ERC7739} for ERC-1271 signature support with ERC-7739 replay protection * * {ERC7821} for performing external calls in batches. * + * TIP: Use {ERC7821} to enable external calls in batches. + * * NOTE: To use this contract, the {ERC7739-_rawSignatureValidation} function must be * implemented using a specific signature verification algorithm. See {SignerECDSA}, {SignerP256} or {SignerRSA}. */ -abstract contract Account is AccountCore, EIP712, ERC721Holder, ERC1155Holder, ERC7739, ERC7821 { +abstract contract Account is AccountCore, EIP712, ERC721Holder, ERC1155Holder, ERC7739 { bytes32 internal constant _PACKED_USER_OPERATION = keccak256( "PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData)" @@ -52,13 +53,4 @@ abstract contract Account is AccountCore, EIP712, ERC721Holder, ERC1155Holder, E ) ); } - - /// @inheritdoc ERC7821 - function _erc7821AuthorizedExecutor( - address caller, - bytes32 mode, - bytes calldata executionData - ) internal view virtual override returns (bool) { - return super._erc7821AuthorizedExecutor(caller, mode, executionData) || caller == address(entryPoint()); - } } diff --git a/contracts/account/extensions/ERC7821.sol b/contracts/account/extensions/ERC7821.sol index 74fff63..8f4568f 100644 --- a/contracts/account/extensions/ERC7821.sol +++ b/contracts/account/extensions/ERC7821.sol @@ -40,6 +40,20 @@ abstract contract ERC7821 is IERC7821 { /** * @dev Access control mechanism for the {execute} function. + * By default, only the contract itself is allowed to execute. + * + * Override this function to implement custom access control, for example to allow the + * ERC-4337 entrypoint to execute. + * + * ```solidity + * function _erc7821AuthorizedExecutor( + * address caller, + * bytes32 mode, + * bytes calldata executionData + * ) internal view virtual override returns (bool) { + * return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + * } + * ``` */ function _erc7821AuthorizedExecutor( address caller, diff --git a/contracts/mocks/account/AccountECDSAMock.sol b/contracts/mocks/account/AccountECDSAMock.sol index 5b3ec70..1e3c600 100644 --- a/contracts/mocks/account/AccountECDSAMock.sol +++ b/contracts/mocks/account/AccountECDSAMock.sol @@ -3,10 +3,20 @@ pragma solidity ^0.8.20; import {Account} from "../../account/Account.sol"; +import {ERC7821} from "../../account/extensions/ERC7821.sol"; import {SignerECDSA} from "../../utils/cryptography/SignerECDSA.sol"; -abstract contract AccountECDSAMock is Account, SignerECDSA { +abstract contract AccountECDSAMock is Account, SignerECDSA, ERC7821 { constructor(address signerAddr) { _initializeSigner(signerAddr); } + + /// @inheritdoc ERC7821 + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } } diff --git a/contracts/mocks/account/AccountERC7702Mock.sol b/contracts/mocks/account/AccountERC7702Mock.sol index a79947f..6dccfc9 100644 --- a/contracts/mocks/account/AccountERC7702Mock.sol +++ b/contracts/mocks/account/AccountERC7702Mock.sol @@ -3,6 +3,16 @@ pragma solidity ^0.8.20; import {Account} from "../../account/Account.sol"; +import {ERC7821} from "../../account/extensions/ERC7821.sol"; import {SignerERC7702} from "../../utils/cryptography/SignerERC7702.sol"; -abstract contract AccountERC7702Mock is Account, SignerERC7702 {} +abstract contract AccountERC7702Mock is Account, SignerERC7702, ERC7821 { + /// @inheritdoc ERC7821 + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } +} diff --git a/contracts/mocks/account/AccountMock.sol b/contracts/mocks/account/AccountMock.sol index ad01d9d..7f88bdd 100644 --- a/contracts/mocks/account/AccountMock.sol +++ b/contracts/mocks/account/AccountMock.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.20; import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +import {ERC7821} from "../../account/extensions/ERC7821.sol"; import {ERC4337Utils} from "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; import {Account} from "../../account/Account.sol"; -abstract contract AccountMock is Account { +abstract contract AccountMock is Account, ERC7821 { /// Validates a user operation with a boolean signature. function _rawSignatureValidation( bytes32 /* userOpHash */, @@ -14,4 +15,13 @@ abstract contract AccountMock is Account { ) internal pure override returns (bool) { return bytes1(signature[0:1]) == bytes1(0x01); } + + /// @inheritdoc ERC7821 + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } } diff --git a/contracts/mocks/account/AccountP256Mock.sol b/contracts/mocks/account/AccountP256Mock.sol index 41c71ca..a924ee5 100644 --- a/contracts/mocks/account/AccountP256Mock.sol +++ b/contracts/mocks/account/AccountP256Mock.sol @@ -3,10 +3,20 @@ pragma solidity ^0.8.20; import {Account} from "../../account/Account.sol"; +import {ERC7821} from "../../account/extensions/ERC7821.sol"; import {SignerP256} from "../../utils/cryptography/SignerP256.sol"; -abstract contract AccountP256Mock is Account, SignerP256 { +abstract contract AccountP256Mock is Account, SignerP256, ERC7821 { constructor(bytes32 qx, bytes32 qy) { _initializeSigner(qx, qy); } + + /// @inheritdoc ERC7821 + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } } diff --git a/contracts/mocks/account/AccountRSAMock.sol b/contracts/mocks/account/AccountRSAMock.sol index 7390355..0592a9c 100644 --- a/contracts/mocks/account/AccountRSAMock.sol +++ b/contracts/mocks/account/AccountRSAMock.sol @@ -3,10 +3,20 @@ pragma solidity ^0.8.20; import {Account} from "../../account/Account.sol"; +import {ERC7821} from "../../account/extensions/ERC7821.sol"; import {SignerRSA} from "../../utils/cryptography/SignerRSA.sol"; -abstract contract AccountRSAMock is Account, SignerRSA { +abstract contract AccountRSAMock is Account, SignerRSA, ERC7821 { constructor(bytes memory e, bytes memory n) { _initializeSigner(e, n); } + + /// @inheritdoc ERC7821 + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } } diff --git a/contracts/mocks/docs/account/MyAccount.sol b/contracts/mocks/docs/account/MyAccount.sol index 7c15ddb..d376ccd 100644 --- a/contracts/mocks/docs/account/MyAccount.sol +++ b/contracts/mocks/docs/account/MyAccount.sol @@ -6,8 +6,9 @@ pragma solidity ^0.8.20; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {Account} from "../../../account/Account.sol"; // or AccountCore +import {ERC7821} from "../../../account/extensions/ERC7821.sol"; -contract MyAccount is Account, Initializable { +contract MyAccount is Account, ERC7821, Initializable { /** * NOTE: EIP-712 domain is set at construction because each account clone * will recalculate its domain separator based on their own address. @@ -25,4 +26,13 @@ contract MyAccount is Account, Initializable { function initializeSigner() public initializer { // Most accounts will require some form of signer initialization logic } + + /// @dev Allows the entry point as an authorized executor. + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } } diff --git a/contracts/mocks/docs/account/MyAccountECDSA.sol b/contracts/mocks/docs/account/MyAccountECDSA.sol index 38829a0..851fd75 100644 --- a/contracts/mocks/docs/account/MyAccountECDSA.sol +++ b/contracts/mocks/docs/account/MyAccountECDSA.sol @@ -5,13 +5,23 @@ pragma solidity ^0.8.20; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {Account} from "../../../account/Account.sol"; +import {ERC7821} from "../../../account/extensions/ERC7821.sol"; import {SignerECDSA} from "../../../utils/cryptography/SignerECDSA.sol"; -contract MyAccountECDSA is Account, SignerECDSA { +contract MyAccountECDSA is Account, SignerECDSA, ERC7821 { constructor() EIP712("MyAccountECDSA", "1") {} function initializeSigner(address signerAddr) public virtual { // Will revert if the signer is already initialized _initializeSigner(signerAddr); } + + /// @dev Allows the entry point as an authorized executor. + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } } diff --git a/contracts/mocks/docs/account/MyAccountP256.sol b/contracts/mocks/docs/account/MyAccountP256.sol index 6082e4d..7e73edc 100644 --- a/contracts/mocks/docs/account/MyAccountP256.sol +++ b/contracts/mocks/docs/account/MyAccountP256.sol @@ -5,13 +5,23 @@ pragma solidity ^0.8.20; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {Account} from "../../../account/Account.sol"; +import {ERC7821} from "../../../account/extensions/ERC7821.sol"; import {SignerP256} from "../../../utils/cryptography/SignerP256.sol"; -contract MyAccountP256 is Account, SignerP256 { +contract MyAccountP256 is Account, SignerP256, ERC7821 { constructor() EIP712("MyAccountP256", "1") {} function initializeSigner(bytes32 qx, bytes32 qy) public virtual { // Will revert if the signer is already initialized _initializeSigner(qx, qy); } + + /// @dev Allows the entry point as an authorized executor. + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } } diff --git a/contracts/mocks/docs/account/MyAccountRSA.sol b/contracts/mocks/docs/account/MyAccountRSA.sol index ece296f..f67e908 100644 --- a/contracts/mocks/docs/account/MyAccountRSA.sol +++ b/contracts/mocks/docs/account/MyAccountRSA.sol @@ -5,13 +5,23 @@ pragma solidity ^0.8.20; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {Account} from "../../../account/Account.sol"; +import {ERC7821} from "../../../account/extensions/ERC7821.sol"; import {SignerRSA} from "../../../utils/cryptography/SignerRSA.sol"; -contract MyAccountRSA is Account, SignerRSA { +contract MyAccountRSA is Account, SignerRSA, ERC7821 { constructor() EIP712("MyAccountRSA", "1") {} function initializeSigner(bytes memory e, bytes memory n) public virtual { // Will revert if the signer is already initialized _initializeSigner(e, n); } + + /// @dev Allows the entry point as an authorized executor. + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } } diff --git a/docs/modules/ROOT/pages/account-abstraction.adoc b/docs/modules/ROOT/pages/account-abstraction.adoc index 94b9b9f..9a0882e 100644 --- a/docs/modules/ROOT/pages/account-abstraction.adoc +++ b/docs/modules/ROOT/pages/account-abstraction.adoc @@ -15,9 +15,10 @@ User operations are validated using an xref:api:utils.adoc#AbstractSigner[`Abstr A more opinionated version is the xref:api:account.adoc#Account[`Account`] contract, which also inherits from: * xref:api:utils.adoc#ERC7739Signer[ERC7739Signer]: An implementation of the https://eips.ethereum.org/EIPS/eip-1271[ERC-1271] interface for smart contract signatures. This layer adds a defensive rehashing mechanism that prevents signatures for this account to be replayed in another account controlled by the same signer. See xref:account-abstraction.adoc#erc7739_signatures[ERC-7739 signatures]. -* https://docs.openzeppelin.com/contracts/api/token/erc721#AccountERC7821[AccountERC7821]: An extension that provides the minimal logic batch multiple calls in a single execution. Useful to execute multiple operations within a single user operation. * https://docs.openzeppelin.com/contracts/api/token/erc721#ERC721Holder[ERC721Holder], https://docs.openzeppelin.com/contracts/api/token/erc1155#ERC1155Holder[ERC1155Holder]: Allows the account to hold https://eips.ethereum.org/EIPS/eip-721[ERC-721] and https://eips.ethereum.org/EIPS/eip-1155[ERC-1155] tokens. +NOTE: The Account doesn't include an execution mechanism. Using xref:api:account.adoc#ERC7821[`ERC7821`] is a recommended solution with the minimal logic to batch multiple calls in a single execution. This is useful to execute multiple calls within a single user operation (e.g. approve and transfer). + [source,solidity] ---- include::api:example$account/MyAccount.sol[] diff --git a/package-lock.json b/package-lock.json index 40b0a51..159c49b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ }, "lib/@openzeppelin-contracts": { "name": "openzeppelin-solidity", - "version": "5.1.0", + "version": "5.2.0", "dev": true, "license": "MIT", "devDependencies": {