Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- `SignerERC7702` is renamed as `SignerEIP7702`. Imports and inheritance must be updated to that new name and path. Behavior is unmodified.
- `ERC721Holder`, `ERC1155Holder`, `ReentrancyGuard` and `ReentrancyGuardTransient` are flagged as stateless and are no longer transpiled. Developers using their upgradeable variants from `@openzeppelin/contracts-upgradeable` must update their imports to use the equivalent version available in `@openzeppelin/contracts`.
- Update minimum pragma to 0.8.24 in `Votes`, `VotesExtended`, `ERC20Votes`, `Strings`, `ERC1155URIStorage`, `MessageHashUtils`, `ERC721URIStorage`, `ERC721Votes`, `ERC721Wrapper`, `ERC721Burnable`, `ERC721Consecutive`, `ERC721Enumerable`, `ERC721Pausable`, `ERC721Royalty`, `ERC721Wrapper`, `EIP712`, `ERC4626` and `ERC7739`. ([#5726](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5726))
- `AccountERC7579`: Installing and uninstalling fallback modules now require the corresponding `initData` and `deInitData` arguments to be at least 4 bytes long (matching the selector to which the fallback module is registered). It now reverts with `ERC7579CannotDecodeFallbackData` instead of treating the missing bytes as `0x00`.

### Deprecation

Expand Down
2 changes: 1 addition & 1 deletion certora/specs/Account.spec
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ invariant absentExecutorIsNotStored(address module, uint256 index)
*/
// This guarantees that at most one fallback module is active for a given initData (i.e. selector)
rule fallbackModule(address module, bytes initData) {
assert isModuleInstalled(FALLBACK_TYPE(), module, initData) <=> getFallbackHandler(getDataSelector(initData)) == module;
assert isModuleInstalled(FALLBACK_TYPE(), module, initData) <=> (initData.length >= 4 && getFallbackHandler(getDataSelector(initData)) == module);
}

rule moduleManagementRule(env e, method f, calldataarg args, uint256 moduleTypeId, address module, bytes additionalContext)
Expand Down
6 changes: 6 additions & 0 deletions contracts/account/extensions/draft-AccountERC7579.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
/// @dev The account's {fallback} was called with a selector that doesn't have an installed handler.
error ERC7579MissingFallbackHandler(bytes4 selector);

/// @dev The provided initData/deInitData for a fallback module is too short to extract a selector.
error ERC7579CannotDecodeFallbackData();

/// @dev Modifier that checks if the caller is an installed module of the given type.
modifier onlyModule(uint256 moduleTypeId, bytes calldata additionalContext) {
_checkModule(moduleTypeId, msg.sender, additionalContext);
Expand Down Expand Up @@ -380,6 +383,8 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
* https://github.com/erc7579/erc7579-implementation/blob/16138d1afd4e9711f6c1425133538837bd7787b5/src/MSAAdvanced.sol#L296[ERC7579 reference implementation].
*
* This is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions.
*
* NOTE: This function expects the signature to be at least 20 bytes long. Panics with {Panic-ARRAY_OUT_OF_BOUNDS} (0x32) otherwise.
*/
function _extractSignatureValidator(
bytes calldata signature
Expand All @@ -399,6 +404,7 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
function _decodeFallbackData(
bytes memory data
) internal pure virtual returns (bytes4 selector, bytes memory remaining) {
require(data.length > 3, ERC7579CannotDecodeFallbackData());
return (bytes4(data), data.slice(4));
}

Expand Down
22 changes: 22 additions & 0 deletions test/account/extensions/AccountERC7579.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ function shouldBehaveLikeAccountERC7579({ withHooks = false } = {}) {
});
}

it('should revert when installing a fallback module with an initData that is not long enough to encode a function selector', async function () {
const instance = this.modules[MODULE_TYPE_FALLBACK];
await expect(
this.mockFromEntrypoint.installModule(MODULE_TYPE_FALLBACK, instance, '0x'),
).to.be.revertedWithCustomError(this.mock, 'ERC7579CannotDecodeFallbackData');

await expect(
this.mockFromEntrypoint.installModule(MODULE_TYPE_FALLBACK, instance, '0x123456'),
).to.be.revertedWithCustomError(this.mock, 'ERC7579CannotDecodeFallbackData');
});

withHooks &&
describe('with hook', function () {
beforeEach(async function () {
Expand Down Expand Up @@ -240,6 +251,17 @@ function shouldBehaveLikeAccountERC7579({ withHooks = false } = {}) {
});
}

it('should revert when uninstalling a fallback module with an initData that is not long enough to encode a function selector', async function () {
const instance = this.modules[MODULE_TYPE_FALLBACK];
await expect(
this.mockFromEntrypoint.uninstallModule(MODULE_TYPE_FALLBACK, instance, '0x'),
).to.be.revertedWithCustomError(this.mock, 'ERC7579CannotDecodeFallbackData');

await expect(
this.mockFromEntrypoint.uninstallModule(MODULE_TYPE_FALLBACK, instance, '0x123456'),
).to.be.revertedWithCustomError(this.mock, 'ERC7579CannotDecodeFallbackData');
});

it('should revert uninstalling a module of type MODULE_TYPE_FALLBACK if a different module was installed for the provided selector', async function () {
const instance = this.modules[MODULE_TYPE_FALLBACK];
const anotherInstance = await ethers.deployContract('$ERC7579ModuleMock', [MODULE_TYPE_FALLBACK]);
Expand Down
Loading