// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules import { ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; // constants import {_INTERFACEID_LSP17_EXTENDABLE} from "./LSP17Constants.sol"; // errors import {NoExtensionFoundForFunctionSelector} from "./LSP17Errors.sol"; /** * @title Module to add more functionalities to a contract using extensions. * * @dev Implementation of the `fallback(...)` logic according to LSP17 - Contract Extension standard. * This module can be inherited to extend the functionality of the parent contract when * calling a function that doesn't exist on the parent contract via forwarding the call * to an extension mapped to the function selector being called, set originally by the parent contract */ abstract contract LSP17ExtendableInitAbstract is ERC165Upgradeable { /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface( bytes4 interfaceId ) public view virtual override returns (bool) { return interfaceId == _INTERFACEID_LSP17_EXTENDABLE || super.supportsInterface(interfaceId); } /** * @dev Returns whether the interfaceId being checked is supported in the extension of the * {supportsInterface} selector. * * To be used by extendable contracts wishing to extend the ERC165 interfaceIds originally * supported by reading whether the interfaceId queried is supported in the `supportsInterface` * extension if the extension is set, if not it returns false. */ function _supportsInterfaceInERC165Extension( bytes4 interfaceId ) internal view virtual returns (bool) { (address erc165Extension, ) = _getExtensionAndForwardValue( ERC165Upgradeable.supportsInterface.selector ); if (erc165Extension == address(0)) return false; return ERC165Checker.supportsERC165InterfaceUnchecked( erc165Extension, interfaceId ); } /** * @dev Returns the extension mapped to a specific function selector * If no extension was found, return the address(0) * To be overridden. * Up to the implementer contract to return an extension based on a function selector */ function _getExtensionAndForwardValue( bytes4 functionSelector ) internal view virtual returns (address, bool); /** * @dev Forwards the call to an extension mapped to a function selector. * * Calls {_getExtensionAndForwardValue} to get the address of the extension mapped to the function selector being * called on the account. If there is no extension, the `address(0)` will be returned. * Forwards the value if the extension is payable. * * Reverts if there is no extension for the function being called. * * If there is an extension for the function selector being called, it calls the extension with the * `CALL` opcode, passing the `msg.data` appended with the 20 bytes of the {msg.sender} and 32 bytes of the `msg.value`. * * @custom:hint This function does not forward to the extension contract the `msg.value` received by the contract that inherits `LSP17Extendable`. * If you would like to forward the `msg.value` to the extension contract, you can override the code of this internal function as follow: * * ```solidity * (bool success, bytes memory result) = extension.call{value: msg.value}( * abi.encodePacked(callData, msg.sender, msg.value) * ); * ``` */ function _fallbackLSP17Extendable( bytes calldata callData ) internal virtual returns (bytes memory) { // If there is a function selector ( address extension, bool shouldForwardValue ) = _getExtensionAndForwardValue(msg.sig); // if no extension was found, revert if (extension == address(0)) revert NoExtensionFoundForFunctionSelector(msg.sig); (bool success, bytes memory result) = extension.call{ value: shouldForwardValue ? msg.value : 0 }(abi.encodePacked(callData, msg.sender, msg.value)); if (success) { return result; } else { // `mload(result)` -> offset in memory where `result.length` is located // `add(result, 32)` -> offset in memory where `result` data starts // solhint-disable no-inline-assembly /// @solidity memory-safe-assembly assembly { let resultdata_size := mload(result) revert(add(result, 32), resultdata_size) } } } }