{
  "language": "Solidity",
  "sources": {
    "contracts/access/Access.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\n/**\n * @title Access\n *\n * @author Inspired by Gnosis Safe.\n *\n * @notice Modifier that only allows this contract to be the 'msg.sender'.\n */\ncontract Access {\n    error Access__notAllowed();\n\n    modifier access() {\n        if (msg.sender != address(this)) revert Access__notAllowed();\n\n        _;\n    }\n}\n"
    },
    "contracts/state/LaserState.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\nimport \"../access/Access.sol\";\nimport \"../common/Utils.sol\";\nimport \"../interfaces/IERC165.sol\";\nimport \"../interfaces/ILaserState.sol\";\n\n/**\n * @title   LaserState\n *\n * @author  Rodrigo Herrera I.\n *\n * @notice  Has all the state(storage) for a Laser wallet and implements\n *          Smart Social Recovery.\n */\ncontract LaserState is ILaserState, Access {\n    address internal constant POINTER = address(0x1); // POINTER for the link list.\n\n    /*//////////////////////////////////////////////////////////////\n                          LASER WALLET STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n    address public singleton;\n\n    address public owner;\n\n    uint256 public nonce;\n\n    uint256 internal guardianCount;\n\n    uint256 internal recoveryOwnerCount;\n\n    mapping(address => address) public guardians;\n\n    mapping(address => address) public recoveryOwners;\n\n    WalletConfig walletConfig;\n\n    /**\n     * @notice Unlocks the wallet. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev    Restricted, can only be called by address(this).\n     */\n    function unlock() external access {\n        address oldOwner = walletConfig.oldOwner;\n        owner = oldOwner;\n\n        walletConfig.isLocked = false;\n        walletConfig.timestamp = 0;\n        walletConfig.oldOwner = address(0);\n\n        emit WalletUnlocked();\n        emit OwnerChanged(oldOwner);\n    }\n\n    /**\n     * @notice Recovers the wallet. Can only be called by the recovery owner + recovery owner\n     *         or recovery owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newOwner  Address of the new owner.\n     */\n    function recover(address newOwner) external access {\n        if (newOwner.code.length != 0 || newOwner == owner || newOwner == address(0)) {\n            revert LS__recover__invalidAddress();\n        }\n\n        walletConfig.isLocked = true;\n        walletConfig.timestamp = block.timestamp;\n        walletConfig.oldOwner = owner;\n\n        owner = newOwner;\n        emit OwnerChanged(newOwner);\n    }\n\n    /**\n     * @notice Changes the owner of the wallet. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newOwner  Address of the new owner.\n     */\n    function changeOwner(address newOwner) external access {\n        if (newOwner.code.length != 0 || newOwner == owner || newOwner == address(0)) {\n            revert LS__changeOwner__invalidAddress();\n        }\n\n        owner = newOwner;\n\n        emit OwnerChanged(newOwner);\n    }\n\n    /**\n     * @notice Changes the singleton. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newSingleton  Address of the new singleton.\n     */\n    function changeSingleton(address newSingleton) external access {\n        //bytes4(keccak256(\"I_AM_LASER\"))\n        if (\n            newSingleton == singleton ||\n            newSingleton == address(this) ||\n            !IERC165(newSingleton).supportsInterface(0xae029e0b)\n        ) revert LS__changeSingleton__invalidAddress();\n\n        singleton = newSingleton;\n\n        emit SingletonChanged(newSingleton);\n    }\n\n    /**\n     * @notice Adds a new guardian. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newGuardian  Address of the new guardian.\n     */\n    function addGuardian(address newGuardian) external access {\n        if (\n            newGuardian == address(0) ||\n            newGuardian == owner ||\n            guardians[newGuardian] != address(0) ||\n            recoveryOwners[newGuardian] != address(0) ||\n            newGuardian == POINTER\n        ) revert LS__addGuardian__invalidAddress();\n\n        if (newGuardian.code.length > 0) {\n            if (!IERC165(newGuardian).supportsInterface(0x1626ba7e)) {\n                revert LS__addGuardian__invalidAddress();\n            }\n        }\n\n        guardians[newGuardian] = guardians[POINTER];\n        guardians[POINTER] = newGuardian;\n\n        unchecked {\n            guardianCount++;\n        }\n\n        emit NewGuardian(newGuardian);\n    }\n\n    /**\n     * @notice Removes a guardian. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param prevGuardian      Address of the previous guardian in the linked list.\n     * @param guardianToRemove  Address of the guardian to be removed.\n     */\n    function removeGuardian(address prevGuardian, address guardianToRemove) external access {\n        if (guardianToRemove == POINTER) {\n            revert LS__removeGuardian__invalidAddress();\n        }\n\n        if (guardians[prevGuardian] != guardianToRemove) {\n            revert LS__removeGuardian__incorrectPreviousGuardian();\n        }\n\n        // There needs to be at least 1 guardian.\n        if (guardianCount - 1 < 1) revert LS__removeGuardian__underflow();\n\n        guardians[prevGuardian] = guardians[guardianToRemove];\n        guardians[guardianToRemove] = address(0);\n\n        unchecked {\n            guardianCount--;\n        }\n\n        emit GuardianRemoved(guardianToRemove);\n    }\n\n    /**\n     * @notice Adds a new recovery owner. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newRecoveryOwner  Address of the new recovery owner.\n     */\n    function addRecoveryOwner(address newRecoveryOwner) external access {\n        if (\n            newRecoveryOwner == address(0) ||\n            newRecoveryOwner == owner ||\n            recoveryOwners[newRecoveryOwner] != address(0) ||\n            guardians[newRecoveryOwner] != address(0) ||\n            newRecoveryOwner == POINTER\n        ) revert LS__addRecoveryOwner__invalidAddress();\n\n        if (newRecoveryOwner.code.length > 0) {\n            if (!IERC165(newRecoveryOwner).supportsInterface(0x1626ba7e)) {\n                revert LS__addRecoveryOwner__invalidAddress();\n            }\n        }\n\n        recoveryOwners[newRecoveryOwner] = recoveryOwners[POINTER];\n        recoveryOwners[POINTER] = newRecoveryOwner;\n\n        unchecked {\n            recoveryOwnerCount++;\n        }\n\n        emit NewRecoveryOwner(newRecoveryOwner);\n    }\n\n    /**\n     * @notice Removes a recovery owner. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param prevRecoveryOwner      Address of the previous recovery owner in the linked list.\n     * @param recoveryOwnerToRemove  Address of the recovery owner to be removed.\n     */\n    function removeRecoveryOwner(address prevRecoveryOwner, address recoveryOwnerToRemove) external access {\n        if (recoveryOwnerToRemove == POINTER) {\n            revert LS__removeRecoveryOwner__invalidAddress();\n        }\n\n        if (recoveryOwners[prevRecoveryOwner] != recoveryOwnerToRemove) {\n            revert LS__removeRecoveryOwner__incorrectPreviousGuardian();\n        }\n\n        // There needs to be at least 1 recovery owner.\n        if (recoveryOwnerCount - 1 < 1) revert LS__removeRecoveryOwner__underflow();\n\n        recoveryOwners[prevRecoveryOwner] = recoveryOwners[recoveryOwnerToRemove];\n        recoveryOwners[recoveryOwnerToRemove] = address(0);\n\n        unchecked {\n            recoveryOwnerCount--;\n        }\n\n        emit RecoveryOwnerRemoved(recoveryOwnerToRemove);\n    }\n\n    /**\n     * @return Array of guardians for this wallet.\n     */\n    function getGuardians() external view returns (address[] memory) {\n        address[] memory guardiansArray = new address[](guardianCount);\n        address currentGuardian = guardians[POINTER];\n\n        uint256 index = 0;\n        while (currentGuardian != POINTER) {\n            guardiansArray[index] = currentGuardian;\n            currentGuardian = guardians[currentGuardian];\n            index++;\n        }\n        return guardiansArray;\n    }\n\n    /**\n     * @return Array of recovery owners for this wallet.\n     */\n    function getRecoveryOwners() external view returns (address[] memory) {\n        address[] memory recoveryOwnersArray = new address[](recoveryOwnerCount);\n        address currentRecoveryOwner = recoveryOwners[POINTER];\n\n        uint256 index = 0;\n        while (currentRecoveryOwner != POINTER) {\n            recoveryOwnersArray[index] = currentRecoveryOwner;\n            currentRecoveryOwner = recoveryOwners[currentRecoveryOwner];\n            index++;\n        }\n        return recoveryOwnersArray;\n    }\n\n    /**\n     * @return\n     * configTimestamp  Time when the recover was triggered.\n     * _isLocked        Boolean if the wallet is currently locked.\n     */\n    function getConfig()\n        external\n        view\n        returns (\n            uint256 configTimestamp,\n            bool _isLocked,\n            address oldOwner\n        )\n    {\n        configTimestamp = walletConfig.timestamp;\n        _isLocked = walletConfig.isLocked;\n        oldOwner = walletConfig.oldOwner;\n    }\n\n    /**\n     * @notice Verifies that the time delay has passed.\n     */\n    function verifyTimelock() internal {\n        uint256 elapsedTime = block.timestamp - walletConfig.timestamp;\n        if (2 days > elapsedTime) revert LS__verifyTimeLock__timeLock();\n\n        walletConfig.isLocked = false;\n        walletConfig.timestamp = 0;\n        walletConfig.oldOwner = address(0);\n    }\n\n    /**\n     * @notice Inits the guardians.\n     *\n     * @param _guardians Array of guardian addresses.\n     */\n    function initGuardians(address[] calldata _guardians) internal {\n        uint256 guardiansLength = _guardians.length;\n        // There needs to be at least 1 guardian.\n        if (guardiansLength < 1) revert LS__initGuardians__underflow();\n\n        address currentGuardian = POINTER;\n\n        for (uint256 i = 0; i < guardiansLength; ) {\n            address guardian = _guardians[i];\n            if (\n                guardian == owner ||\n                guardian == address(0) ||\n                guardian == POINTER ||\n                guardian == currentGuardian ||\n                guardians[guardian] != address(0)\n            ) revert LS__initGuardians__invalidAddress();\n\n            if (guardian.code.length > 0) {\n                // If the guardian is a smart contract wallet, it needs to support EIP1271.\n                if (!IERC165(guardian).supportsInterface(0x1626ba7e)) {\n                    revert LS__initGuardians__invalidAddress();\n                }\n            }\n\n            unchecked {\n                i++;\n            }\n            guardians[currentGuardian] = guardian;\n            currentGuardian = guardian;\n        }\n\n        guardians[currentGuardian] = POINTER;\n        guardianCount = guardiansLength;\n    }\n\n    /**\n     * @notice Inits the recovery owners.\n     *\n     * @param _recoveryOwners Array of recovery owner addresses.\n     */\n    function initRecoveryOwners(address[] calldata _recoveryOwners) internal {\n        uint256 recoveryOwnersLength = _recoveryOwners.length;\n        // There needs to be at least 1 recovery owner.\n        if (recoveryOwnersLength < 1) revert LS__initRecoveryOwners__underflow();\n\n        address currentRecoveryOwner = POINTER;\n\n        for (uint256 i = 0; i < recoveryOwnersLength; ) {\n            address recoveryOwner = _recoveryOwners[i];\n            if (\n                recoveryOwner == owner ||\n                recoveryOwner == address(0) ||\n                recoveryOwner == POINTER ||\n                recoveryOwner == currentRecoveryOwner ||\n                recoveryOwners[recoveryOwner] != address(0) ||\n                guardians[recoveryOwner] != address(0)\n            ) revert LS__initRecoveryOwners__invalidAddress();\n\n            if (recoveryOwner.code.length > 0) {\n                // If the recovery owner is a smart contract wallet, it needs to support EIP1271.\n                if (!IERC165(recoveryOwner).supportsInterface(0x1626ba7e)) {\n                    revert LS__initRecoveryOwners__invalidAddress();\n                }\n            }\n\n            unchecked {\n                i++;\n            }\n            recoveryOwners[currentRecoveryOwner] = recoveryOwner;\n            currentRecoveryOwner = recoveryOwner;\n        }\n\n        recoveryOwners[currentRecoveryOwner] = POINTER;\n        recoveryOwnerCount = recoveryOwnersLength;\n    }\n\n    /**\n     * @notice Activates the wallet for the first time.\n     *\n     * @dev    Cannot be called after initialization.\n     */\n    function activateWallet(\n        address _owner,\n        address[] calldata _guardians,\n        address[] calldata _recoveryOwners\n    ) internal {\n        // If owner is not address(0), the wallet is already active.\n        if (owner != address(0)) revert LS__activateWallet__walletInitialized();\n\n        if (_owner.code.length != 0) {\n            revert LS__activateWallet__invalidOwnerAddress();\n        }\n\n        // We set the owner. There is no need for further verification.\n        owner = _owner;\n\n        // We init the guardians.\n        initGuardians(_guardians);\n\n        // We init the recovery owners.\n        initRecoveryOwners(_recoveryOwners);\n    }\n}\n"
    },
    "contracts/common/Utils.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\nimport \"../interfaces/IEIP1271.sol\";\n\n/**\n * @title Utils\n *\n * @notice Helper functions for Laser wallet.\n */\nlibrary Utils {\n    /*//////////////////////////////////////////////////////////////\n                                 ERRORS\n    //////////////////////////////////////////////////////////////*/\n\n    error Utils__returnSigner__invalidSignature();\n\n    error Utils__returnSigner__invalidContractSignature();\n\n    /**\n     * @param signedHash  The hash that was signed.\n     * @param signatures  Result of signing the has.\n     * @param pos         Position of the signer.\n     *\n     * @return signer      Address that signed the hash.\n     */\n    function returnSigner(\n        bytes32 signedHash,\n        bytes memory signatures,\n        uint256 pos\n    ) internal view returns (address signer) {\n        bytes32 r;\n        bytes32 s;\n        uint8 v;\n        (r, s, v) = splitSigs(signatures, pos);\n\n        if (v == 0) {\n            // If v is 0, then it is a contract signature.\n            // The address of the contract is encoded into r.\n            signer = address(uint160(uint256(r)));\n\n            // The signature(s) of the EOA's that control the target contract.\n            bytes memory contractSignature;\n\n            assembly {\n                contractSignature := add(signatures, s)\n            }\n\n            if (IEIP1271(signer).isValidSignature(signedHash, contractSignature) != 0x1626ba7e) {\n                revert Utils__returnSigner__invalidContractSignature();\n            }\n        } else if (v > 30) {\n            signer = ecrecover(\n                keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", signedHash)),\n                v - 4,\n                r,\n                s\n            );\n        } else {\n            signer = ecrecover(signedHash, v, r, s);\n        }\n\n        if (signer == address(0)) revert Utils__returnSigner__invalidSignature();\n    }\n\n    /**\n     * @dev Returns the r, s and v values of the signature.\n     *\n     * @param pos Which signature to read.\n     */\n    function splitSigs(bytes memory signatures, uint256 pos)\n        internal\n        pure\n        returns (\n            bytes32 r,\n            bytes32 s,\n            uint8 v\n        )\n    {\n        assembly {\n            let sigPos := mul(0x41, pos)\n            r := mload(add(signatures, add(sigPos, 0x20)))\n            s := mload(add(signatures, add(sigPos, 0x40)))\n            v := byte(0, mload(add(signatures, add(sigPos, 0x60))))\n        }\n    }\n\n    /**\n     * @dev Calls a target address, sends value and / or data payload.\n     *\n     * @param to        Destination address.\n     * @param value     Amount in WEI to transfer.\n     * @param callData  Data payload for the transaction.\n     */\n    function call(\n        address to,\n        uint256 value,\n        bytes memory callData,\n        uint256 txGas\n    ) internal returns (bool success) {\n        assembly {\n            success := call(txGas, to, value, add(callData, 0x20), mload(callData), 0, 0)\n        }\n    }\n}\n"
    },
    "contracts/interfaces/IERC165.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity 0.8.16;\n\n/**\n * @title IERC165\n * @notice Support of ERC165.\n */\ninterface IERC165 {\n    /**\n     * @notice Query if a contract implements an interface\n     *\n     * @param interfaceID The interface identifier, as specified in ERC-165\n     *\n     * @dev Interface identification is specified in ERC-165. This function\n     * uses less than 30,000 gas.\n     *\n     * @return `true` if the contract implements `interfaceID` and\n     * interfaceID` is not 0xffffffff, `false` otherwise\n     */\n    function supportsInterface(bytes4 interfaceID) external view returns (bool);\n}\n"
    },
    "contracts/interfaces/ILaserState.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\n/**\n * @notice Wallet configuration for the recovery mechanism.\n *\n * @param isLocked  Boolean if the wallet is currently locked.\n * @param timestamp The time (block.timestamp) when the wallet was locked.\n */\nstruct WalletConfig {\n    bool isLocked;\n    uint256 timestamp;\n    address oldOwner;\n}\n\n/**\n * @title   LaserState\n *\n * @author  Rodrigo Herrera I.\n *\n * @notice  Has all the state(storage) for a Laser wallet and implements\n *          Smart Social Recovery.\n *\n * @dev    This interface has all events, errors, and external function for LaserState.\n */\ninterface ILaserState {\n    /*//////////////////////////////////////////////////////////////\n                                 EVENTS\n    //////////////////////////////////////////////////////////////*/\n\n    event WalletUnlocked();\n\n    event RecoverActivated(address newOwner);\n\n    event OwnerChanged(address newOwner);\n\n    event SingletonChanged(address newSingleton);\n\n    event NewGuardian(address newGuardian);\n\n    event GuardianRemoved(address removedGuardian);\n\n    event NewRecoveryOwner(address NewRecoveryOwner);\n\n    event RecoveryOwnerRemoved(address removedRecoveryOwner);\n\n    /*//////////////////////////////////////////////////////////////\n                                 ERRORS\n    //////////////////////////////////////////////////////////////*/\n\n    error LS__recover__invalidAddress();\n\n    error LS__changeOwner__invalidAddress();\n\n    error LS__changeSingleton__invalidAddress();\n\n    error LS__addGuardian__invalidAddress();\n\n    error LS__removeGuardian__invalidAddress();\n\n    error LS__removeGuardian__incorrectPreviousGuardian();\n\n    error LS__removeGuardian__underflow();\n\n    error LS__addRecoveryOwner__invalidAddress();\n\n    error LS__removeRecoveryOwner__invalidAddress();\n\n    error LS__removeRecoveryOwner__incorrectPreviousGuardian();\n\n    error LS__verifyTimeLock__timeLock();\n\n    error LS__removeRecoveryOwner__underflow();\n\n    error LS__initGuardians__underflow();\n\n    error LS__initGuardians__invalidAddress();\n\n    error LS__initRecoveryOwners__underflow();\n\n    error LS__initRecoveryOwners__invalidAddress();\n\n    error LS__activateWallet__walletInitialized();\n\n    error LS__activateWallet__invalidOwnerAddress();\n\n    /*//////////////////////////////////////////////////////////////\n                                 STATE\n    //////////////////////////////////////////////////////////////*/\n\n    function singleton() external view returns (address);\n\n    function owner() external view returns (address);\n\n    function nonce() external view returns (uint256);\n\n    /*//////////////////////////////////////////////////////////////\n                                EXTERNAL\n    //////////////////////////////////////////////////////////////*/\n\n    /**\n     * @notice Unlocks the wallet. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev    Restricted, can only be called by address(this).\n     */\n    function unlock() external;\n\n    /**\n     * @notice Recovers the wallet. Can only be called by the recovery owner + recovery owner\n     *         or recovery owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newOwner  Address of the new owner.\n     */\n    function recover(address newOwner) external;\n\n    /**\n     * @notice Changes the owner of the wallet. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newOwner  Address of the new owner.\n     */\n    function changeOwner(address newOwner) external;\n\n    /**\n     * @notice Changes the singleton. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newSingleton  Address of the new singleton.\n     */\n    function changeSingleton(address newSingleton) external;\n\n    /**\n     * @notice Adds a new guardian. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newGuardian  Address of the new guardian.\n     */\n    function addGuardian(address newGuardian) external;\n\n    /**\n     * @notice Removes a guardian. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param prevGuardian      Address of the previous guardian in the linked list.\n     * @param guardianToRemove  Address of the guardian to be removed.\n     */\n    function removeGuardian(address prevGuardian, address guardianToRemove) external;\n\n    /**\n     * @notice Adds a new recovery owner. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param newRecoveryOwner  Address of the new recovery owner.\n     */\n    function addRecoveryOwner(address newRecoveryOwner) external;\n\n    /**\n     * @notice Removes a recovery owner. Can only be called by the owner + recovery owner\n     *         or owner + guardian.\n     *\n     * @dev   Restricted, can only be called by address(this).\n     *\n     * @param prevRecoveryOwner      Address of the previous recovery owner in the linked list.\n     * @param recoveryOwnerToRemove  Address of the recovery owner to be removed.\n     */\n    function removeRecoveryOwner(address prevRecoveryOwner, address recoveryOwnerToRemove) external;\n\n    /**\n     * @return Array of guardians for this wallet.\n     */\n    function getGuardians() external view returns (address[] memory);\n\n    /**\n     * @return Array of recovery owners for this wallet.\n     */\n    function getRecoveryOwners() external view returns (address[] memory);\n\n    /**\n     * @return\n     * configTimestamp  Time when the recover was triggered.\n     * _isLocked        Boolean if the wallet is currently locked.\n     */\n    function getConfig()\n        external\n        view\n        returns (\n            uint256 configTimestamp,\n            bool _isLocked,\n            address oldOwner\n        );\n}\n"
    },
    "contracts/interfaces/IEIP1271.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\n/**\n * @title IEIP1271\n *\n * @notice Interface to call external contracts to validate signature.\n */\ninterface IEIP1271 {\n    /**\n     * @notice Should return whether the signature provided is valid for the provided hash.\n     *\n     * @param hash      Hash of the data to be signed.\n     * @param signature Signature byte array associated with hash.\n     *\n     * MUST return the bytes4 magic value 0x1626ba7e when function passes.\n     * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)\n     * MUST allow external calls\n     *\n     * @return Magic value.\n     */\n    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4);\n}\n"
    },
    "contracts/storage/LaserWalletStorage.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\nimport {WalletConfig} from \"../interfaces/ILaserState.sol\";\n\n/**\n * @title LaserWalletStorage\n *\n * @notice Contract that maps the storage of Laser wallet.\n */\nabstract contract LaserWalletStorage {\n    // LaserState.sol\n    address public singleton;\n\n    address public owner;\n\n    uint256 public nonce;\n\n    uint256 internal guardianCount;\n\n    uint256 internal recoveryOwnerCount;\n\n    mapping(address => address) public guardians;\n\n    mapping(address => address) public recoveryOwners;\n\n    WalletConfig walletConfig;\n}\n"
    },
    "contracts/helper/LaserHelper.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\nimport \"../interfaces/IEIP1271.sol\";\nimport \"../interfaces/ILaserState.sol\";\n\n/**\n * @title LaserHelper\n *\n * @notice Allows to batch multiple requests in a single rpc call.\n */\ncontract LaserHelper {\n    error Utils__returnSigner__invalidSignature();\n\n    error Utils__returnSigner__invalidContractSignature();\n\n    // @notice This is temporary, all of this code does not go here.\n\n    /**\n     * @param signedHash  The hash that was signed.\n     * @param signatures  Result of signing the has.\n     * @param pos         Position of the signer.\n     *\n     * @return signer      Address that signed the hash.\n     */\n    function returnSigner(\n        bytes32 signedHash,\n        bytes memory signatures,\n        uint256 pos\n    ) external view returns (address signer) {\n        bytes32 r;\n        bytes32 s;\n        uint8 v;\n        (r, s, v) = splitSigs(signatures, pos);\n\n        if (v == 0) {\n            // If v is 0, then it is a contract signature.\n            // The address of the contract is encoded into r.\n            signer = address(uint160(uint256(r)));\n\n            // The signature(s) of the EOA's that control the target contract.\n            bytes memory contractSignature;\n\n            assembly {\n                contractSignature := add(signatures, s)\n            }\n\n            if (IEIP1271(signer).isValidSignature(signedHash, contractSignature) != 0x1626ba7e) {\n                revert Utils__returnSigner__invalidContractSignature();\n            }\n        } else if (v > 30) {\n            signer = ecrecover(\n                keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", signedHash)),\n                v - 4,\n                r,\n                s\n            );\n        } else {\n            signer = ecrecover(signedHash, v, r, s);\n        }\n\n        if (signer == address(0)) revert Utils__returnSigner__invalidSignature();\n    }\n\n    /**\n     * @dev Returns the r, s and v values of the signature.\n     *\n     * @param pos Which signature to read.\n     */\n    function splitSigs(bytes memory signatures, uint256 pos)\n        public\n        pure\n        returns (\n            bytes32 r,\n            bytes32 s,\n            uint8 v\n        )\n    {\n        assembly {\n            let sigPos := mul(0x41, pos)\n            r := mload(add(signatures, add(sigPos, 0x20)))\n            s := mload(add(signatures, add(sigPos, 0x40)))\n            v := byte(0, mload(add(signatures, add(sigPos, 0x60))))\n        }\n    }\n\n    function getLaserState(address laserWallet)\n        external\n        view\n        returns (\n            address owner,\n            address[] memory guardians,\n            address[] memory recoveryOwners,\n            address singleton,\n            bool _isLocked,\n            uint256 configTimestamp,\n            uint256 nonce,\n            uint256 balance,\n            address oldOwner\n        )\n    {\n        ILaserState laser = ILaserState(laserWallet);\n\n        owner = laser.owner();\n        guardians = laser.getGuardians();\n        recoveryOwners = laser.getRecoveryOwners();\n        singleton = laser.singleton();\n        (configTimestamp, _isLocked, oldOwner) = laser.getConfig();\n        nonce = laser.nonce();\n        balance = address(laserWallet).balance;\n    }\n}\n"
    },
    "contracts/test/MockUtils.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\nimport \"../interfaces/IEIP1271.sol\";\n\n/**\n * @title Utils - Helper functions for Laser wallet and modules.\n */\ncontract MockUtils {\n    /*//////////////////////////////////////////////////////////////\n                                 ERRORS\n    //////////////////////////////////////////////////////////////*/\n\n    error Utils__returnSigner__invalidSignature();\n\n    error Utils__returnSigner__invalidContractSignature();\n\n    /**\n     * @param signedHash  The hash that was signed.\n     * @param signatures  Result of signing the has.\n     * @param pos         Position of the signer.\n     *\n     * @return signer      Address that signed the hash.\n     */\n    function returnSigner(\n        bytes32 signedHash,\n        bytes memory signatures,\n        uint256 pos\n    ) external view returns (address signer) {\n        bytes32 r;\n        bytes32 s;\n        uint8 v;\n        (r, s, v) = splitSigs(signatures, pos);\n\n        if (v == 0) {\n            // If v is 0, then it is a contract signature.\n            // The address of the contract is encoded into r.\n            signer = address(uint160(uint256(r)));\n\n            // The signature(s) of the EOA's that control the target contract.\n            bytes memory contractSignature;\n\n            assembly {\n                contractSignature := add(signatures, s)\n            }\n\n            if (IEIP1271(signer).isValidSignature(signedHash, contractSignature) != 0x1626ba7e) {\n                revert Utils__returnSigner__invalidContractSignature();\n            }\n        } else if (v > 30) {\n            signer = ecrecover(\n                keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", signedHash)),\n                v - 4,\n                r,\n                s\n            );\n        } else {\n            signer = ecrecover(signedHash, v, r, s);\n        }\n\n        if (signer == address(0)) revert Utils__returnSigner__invalidSignature();\n    }\n\n    /**\n     * @dev Returns the r, s and v values of the signature.\n     *\n     * @param pos Which signature to read.\n     */\n    function splitSigs(bytes memory signatures, uint256 pos)\n        public\n        pure\n        returns (\n            bytes32 r,\n            bytes32 s,\n            uint8 v\n        )\n    {\n        assembly {\n            let sigPos := mul(0x41, pos)\n            r := mload(add(signatures, add(sigPos, 0x20)))\n            s := mload(add(signatures, add(sigPos, 0x40)))\n            v := byte(0, mload(add(signatures, add(sigPos, 0x60))))\n        }\n    }\n\n    /**\n     * @dev Calls a target address, sends value and / or data payload.\n     *\n     * @param to        Destination address.\n     * @param value     Amount in WEI to transfer.\n     * @param callData  Data payload for the transaction.\n     */\n    function call(\n        address to,\n        uint256 value,\n        bytes memory callData,\n        uint256 txGas\n    ) external returns (bool success) {\n        assembly {\n            success := call(txGas, to, value, add(callData, 0x20), mload(callData), 0, 0)\n        }\n    }\n}\n"
    },
    "contracts/proxies/LaserFactory.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\nimport \"../interfaces/IERC165.sol\";\nimport \"../interfaces/ILaserFactory.sol\";\nimport \"../interfaces/ILaserWallet.sol\";\n\n/**\n * @title LaserFactory\n *\n * @notice Factory that creates new Laser proxies, and has helper methods.\n */\ncontract LaserFactory is ILaserFactory {\n    address public immutable singleton;\n\n    /**\n     * @param _singleton Base contract.\n     */\n    constructor(address _singleton) {\n        // Laser Wallet contract: bytes4(keccak256(\"I_AM_LASER\"))\n        if (!IERC165(_singleton).supportsInterface(0xae029e0b)) {\n            revert LF__constructor__invalidSingleton();\n        }\n        singleton = _singleton;\n    }\n\n    /**\n     * @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.\n     *\n     * @param initializer   Payload for message call sent to new proxy contract.\n     * @param saltNonce     Nonce that will be used to generate the salt to calculate the address of the new proxy contract.\n     */\n    function createProxy(bytes memory initializer, uint256 saltNonce) external returns (LaserProxy proxy) {\n        proxy = deployProxy(initializer, saltNonce);\n\n        bool success;\n        assembly {\n            // We initialize the wallet in a single call.\n            success := call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0)\n        }\n\n        if (!success) revert LF__createProxy__creationFailed();\n\n        emit LaserCreated(address(proxy));\n    }\n\n    /**\n     * @dev Precomputes the address of a proxy that is created through 'create2'.\n     */\n    function preComputeAddress(bytes memory initializer, uint256 saltNonce) external view returns (address) {\n        bytes memory creationCode = proxyCreationCode();\n        bytes memory data = abi.encodePacked(creationCode, uint256(uint160(singleton)));\n\n        bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));\n\n        bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(data)));\n\n        return address(uint160(uint256(hash)));\n    }\n\n    /**\n     * @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed.\n     */\n    function proxyRuntimeCode() external pure returns (bytes memory) {\n        return type(LaserProxy).runtimeCode;\n    }\n\n    /**\n     *  @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address.\n     */\n    function proxyCreationCode() public pure returns (bytes memory) {\n        return type(LaserProxy).creationCode;\n    }\n\n    /**\n     * @notice Allows to create new proxy contact using CREATE2 but it doesn't run the initializer.\n     *         This method is only meant as an utility to be called from other methods.\n     *\n     * @param initializer Payload for message call sent to new proxy contract.\n     * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.\n     */\n    function deployProxy(bytes memory initializer, uint256 saltNonce) internal returns (LaserProxy proxy) {\n        // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it\n        bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));\n\n        bytes memory deploymentData = abi.encodePacked(type(LaserProxy).creationCode, uint256(uint160(singleton)));\n\n        assembly {\n            proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)\n        }\n\n        if (address(proxy) == address(0)) revert LF__deployProxy__create2Failed();\n    }\n}\n"
    },
    "contracts/interfaces/ILaserFactory.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\nimport \"../proxies/LaserProxy.sol\";\n\n/**\n * @title  LaserFactory\n *\n * @notice Factory that creates new Laser proxies, and has helper methods.\n *\n * @dev    This interface has all events, errors, and external function for LaserFactory.\n */\ninterface ILaserFactory {\n    /*//////////////////////////////////////////////////////////////\n                                 EVENTS\n    //////////////////////////////////////////////////////////////*/\n\n    event LaserCreated(address laser);\n\n    /*//////////////////////////////////////////////////////////////\n                                 ERRORS\n    //////////////////////////////////////////////////////////////*/\n\n    error LF__constructor__invalidSingleton();\n\n    error LF__createProxy__creationFailed();\n\n    error LF__deployProxy__create2Failed();\n\n    /*//////////////////////////////////////////////////////////////\n                                 STATE\n    //////////////////////////////////////////////////////////////*/\n\n    function singleton() external view returns (address);\n\n    /*//////////////////////////////////////////////////////////////\n                                EXTERNAL\n    //////////////////////////////////////////////////////////////*/\n\n    /**\n     * @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.\n     *\n     * @param initializer   Payload for message call sent to new proxy contract.\n     * @param saltNonce     Nonce that will be used to generate the salt to calculate the address of the new proxy contract.\n     */\n    function createProxy(bytes memory initializer, uint256 saltNonce) external returns (LaserProxy proxy);\n\n    /**\n     * @dev Precomputes the address of a proxy that is created through 'create2'.\n     */\n    function preComputeAddress(bytes memory initializer, uint256 saltNonce) external view returns (address);\n\n    /**\n     * @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed.\n     */\n    function proxyRuntimeCode() external pure returns (bytes memory);\n\n    /**\n     *  @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address.\n     */\n    function proxyCreationCode() external pure returns (bytes memory);\n}\n"
    },
    "contracts/interfaces/ILaserWallet.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity 0.8.16;\n\nstruct Transaction {\n    address to;\n    uint256 value;\n    bytes callData;\n    uint256 nonce;\n    bytes signatures;\n}\n\n/**\n * @title  ILaserWallet\n *\n * @author Rodrigo Herrera I.\n *\n * @notice Laser is a secure smart contract wallet (vault) made for the Ethereum Virtual Machine.\n *\n * @dev    This interface has all events, errors, and external function for LaserWallet.\n */\ninterface ILaserWallet {\n    /*//////////////////////////////////////////////////////////////\n                                 EVENTS\n    //////////////////////////////////////////////////////////////*/\n\n    event ExecSuccess(address to, uint256 value, uint256 nonce, bytes4 funcSig);\n\n    /*//////////////////////////////////////////////////////////////\n                                 ERRORS\n    //////////////////////////////////////////////////////////////*/\n\n    error LW__init__notOwner();\n\n    error LW__exec__invalidNonce();\n\n    error LW__exec__walletLocked();\n\n    error LW__exec__invalidSignatureLength();\n\n    error LW__exec__invalidSignature();\n\n    error LW__exec__callFailed();\n\n    error LW__recovery__invalidNonce();\n\n    error LW__recovery__invalidSignatureLength();\n\n    error LW__recovery__duplicateSigner();\n\n    error LW__recoveryLock__invalidSignature();\n\n    error LW__recoveryUnlock__time();\n\n    error LW__recoveryUnlock__invalidSignature();\n\n    error LW__recoveryRecover__walletLocked();\n\n    error LW__recoveryRecover__invalidSignature();\n\n    error LW__recovery__invalidOperation();\n\n    error LW__recovery__callFailed();\n\n    error LaserWallet__invalidSignature();\n\n    /*//////////////////////////////////////////////////////////////\n                                EXTERNAL\n    //////////////////////////////////////////////////////////////*/\n\n    /**\n     * @notice Setup function, sets initial storage of the wallet.\n     *         It can't be called after initialization.\n     *\n     * @param _owner           The owner of the wallet.\n     * @param _guardians       Array of guardians.\n     * @param _recoveryOwners  Array of recovery owners.\n     * @param ownerSignature   Signature of the owner that validates the correctness of the address.\n     */\n    function init(\n        address _owner,\n        address[] calldata _guardians,\n        address[] calldata _recoveryOwners,\n        bytes calldata ownerSignature\n    ) external;\n\n    /**\n     * @notice Executes a generic transaction.\n     *         The transaction is required to be signed by the owner + recovery owner or owner + guardian\n     *         while the wallet is not locked.\n     *\n     * @param to         Destination address.\n     * @param value      Amount in WEI to transfer.\n     * @param callData   Data payload to send.\n     * @param _nonce     Anti-replay number.\n     * @param signatures Signatures of the hash of the transaction.\n     */\n    function exec(\n        address to,\n        uint256 value,\n        bytes calldata callData,\n        uint256 _nonce,\n        bytes calldata signatures\n    ) external returns (bool success);\n\n    /**\n     * @notice Executes a batch of transactions.\n     *\n     * @param transactions An array of Laser transactions.\n     */\n    function multiCall(Transaction[] calldata transactions) external;\n\n    /**\n     * @notice Triggers the recovery mechanism.\n     *\n     * @param callData   Data payload, can only be either lock(), unlock() or recover().\n     * @param signatures Signatures of the hash of the transaction.\n     */\n    function recovery(\n        uint256 _nonce,\n        bytes calldata callData,\n        bytes calldata signatures\n    ) external;\n\n    /**\n     * @notice Returns the hash to be signed to execute a transaction.\n     */\n    function operationHash(\n        address to,\n        uint256 value,\n        bytes calldata callData,\n        uint256 _nonce\n    ) external view returns (bytes32);\n\n    /**\n     * @notice Should return whether the signature provided is valid for the provided hash.\n     *\n     * @param hash      Hash of the data to be signed.\n     * @param signature Signature byte array associated with hash.\n     *\n     * MUST return the bytes4 magic value 0x1626ba7e when function passes.\n     * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)\n     * MUST allow external calls\n     *\n     * @return Magic value.\n     */\n    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4);\n\n    /**\n     * @return chainId The chain id of this.\n     */\n    function getChainId() external view returns (uint256 chainId);\n\n    /**\n     * @notice Domain separator for this wallet.\n     */\n    function domainSeparator() external view returns (bytes32);\n}\n"
    },
    "contracts/proxies/LaserProxy.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\n/**\n * @title LaserProxy\n *\n * @notice Proxy contract that delegates all calls to a master copy.\n */\ncontract LaserProxy {\n    // The singleton always needs to be at storage slot 0.\n    address internal singleton;\n\n    /**\n     * @param _singleton Singleton address.\n     */\n    constructor(address _singleton) {\n        // The proxy creation is done through the LaserProxyFactory.\n        // The singleton is created at the factory's creation, so there is no need to do checks here.\n        singleton = _singleton;\n    }\n\n    /**\n     * @dev Fallback function forwards all transactions and returns all received return data.\n     */\n    fallback() external payable {\n        address _singleton = singleton;\n        assembly {\n            calldatacopy(0, 0, calldatasize())\n            let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)\n            returndatacopy(0, 0, returndatasize())\n            if eq(success, 0) {\n                revert(0, returndatasize())\n            }\n            return(0, returndatasize())\n        }\n    }\n}\n"
    },
    "contracts/LaserWallet.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\nimport \"./handlers/Handler.sol\";\nimport \"./interfaces/ILaserWallet.sol\";\nimport \"./state/LaserState.sol\";\n\n/**\n * @title  LaserWallet\n *\n * @author Rodrigo Herrera I.\n *\n * @notice Laser is a secure smart contract wallet (vault) made for the Ethereum Virtual Machine.\n */\ncontract LaserWallet is ILaserWallet, LaserState, Handler {\n    /*//////////////////////////////////////////////////////////////\n                             LASER METADATA\n    //////////////////////////////////////////////////////////////*/\n\n    string public constant VERSION = \"1.0.0\";\n\n    string public constant NAME = \"Laser Wallet\";\n\n    /*//////////////////////////////////////////////////////////////\n                            SIGNATURE TYPES\n    //////////////////////////////////////////////////////////////*/\n\n    bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH =\n        keccak256(\"EIP712Domain(uint256 chainId,address verifyingContract)\");\n\n    bytes32 private constant LASER_TYPE_STRUCTURE =\n        keccak256(\"LaserOperation(address to,uint256 value,bytes callData,uint256 nonce)\");\n\n    /**\n     * @dev Sets the owner of the implementation address (singleton) to 'this'.\n     *      This will make the base contract unusable, even though it does not have 'delegatecall'.\n     */\n    constructor() {\n        owner = address(this);\n    }\n\n    receive() external payable {}\n\n    /**\n     * @notice Setup function, sets initial storage of the wallet.\n     *         It can't be called after initialization.\n     *\n     * @param _owner           The owner of the wallet.\n     * @param _guardians       Array of guardians.\n     * @param _recoveryOwners  Array of recovery owners.\n     * @param ownerSignature   Signature of the owner that validates the correctness of the address.\n     */\n    function init(\n        address _owner,\n        address[] calldata _guardians,\n        address[] calldata _recoveryOwners,\n        bytes calldata ownerSignature\n    ) external {\n        // activateWallet verifies that the current owner is address 0, reverts otherwise.\n        // This is more than enough to avoid being called after initialization.\n        activateWallet(_owner, _guardians, _recoveryOwners);\n\n        // This is primarily to verify that the owner address is correct.\n        // It also provides some extra security guarantes (the owner really approved the guardians and recovery owners).\n        bytes32 signedHash = keccak256(abi.encodePacked(_guardians, _recoveryOwners, block.chainid));\n\n        address signer = Utils.returnSigner(signedHash, ownerSignature, 0);\n\n        if (signer != _owner) revert LW__init__notOwner();\n    }\n\n    /**\n     * @notice Executes a generic transaction.\n     *         The transaction is required to be signed by the owner + recovery owner or owner + guardian\n     *         while the wallet is not locked.\n     *\n     * @param to         Destination address.\n     * @param value      Amount in WEI to transfer.\n     * @param callData   Data payload to send.\n     * @param _nonce     Anti-replay number.\n     * @param signatures Signatures of the hash of the transaction.\n     */\n    function exec(\n        address to,\n        uint256 value,\n        bytes calldata callData,\n        uint256 _nonce,\n        bytes calldata signatures\n    ) public returns (bool success) {\n        // We immediately increase the nonce to avoid replay attacks.\n        unchecked {\n            if (nonce++ != _nonce) revert LW__exec__invalidNonce();\n        }\n\n        if (walletConfig.isLocked) {\n            verifyTimelock();\n        }\n\n        // We get the hash for this transaction.\n        bytes32 signedHash = keccak256(encodeOperation(to, value, callData, _nonce));\n\n        if (signatures.length < 130) revert LW__exec__invalidSignatureLength();\n\n        address signer1 = Utils.returnSigner(signedHash, signatures, 0);\n        address signer2 = Utils.returnSigner(signedHash, signatures, 1);\n\n        if (signer1 != owner || (recoveryOwners[signer2] == address(0) && guardians[signer2] == address(0))) {\n            revert LW__exec__invalidSignature();\n        }\n\n        success = Utils.call(to, value, callData, gasleft());\n        if (!success) revert LW__exec__callFailed();\n\n        emit ExecSuccess(to, value, nonce, bytes4(callData));\n    }\n\n    /**\n     * @notice Executes a batch of transactions.\n     *\n     * @param transactions An array of Laser transactions.\n     */\n    function multiCall(Transaction[] calldata transactions) external {\n        uint256 transactionsLength = transactions.length;\n\n        // @todo custom errors and optimization.\n        // This is a mockup, not final.\n        for (uint256 i = 0; i < transactionsLength; ) {\n            Transaction calldata transaction = transactions[i];\n\n            exec(transaction.to, transaction.value, transaction.callData, transaction.nonce, transaction.signatures);\n\n            unchecked {\n                ++i;\n            }\n        }\n    }\n\n    /**\n     * @notice Triggers the recovery mechanism.\n     *\n     * @param callData   Data payload, can only be either lock(), unlock() or recover(address).\n     * @param signatures Signatures of the hash of the transaction.\n     */\n    function recovery(\n        uint256 _nonce,\n        bytes calldata callData,\n        bytes calldata signatures\n    ) external {\n        // We immediately increase the nonce to avoid replay attacks.\n        unchecked {\n            if (nonce++ != _nonce) revert LW__recovery__invalidNonce();\n        }\n\n        bytes4 functionSelector = bytes4(callData);\n\n        // All calls require at least 2 signatures.\n        if (signatures.length < 130) revert LW__recovery__invalidSignatureLength();\n\n        bytes32 signedHash = keccak256(abi.encodePacked(_nonce, keccak256(callData), address(this), block.chainid));\n\n        address signer1 = Utils.returnSigner(signedHash, signatures, 0);\n        address signer2 = Utils.returnSigner(signedHash, signatures, 1);\n\n        if (signer1 == signer2) revert LW__recovery__duplicateSigner();\n\n        if (functionSelector == 0xa69df4b5) {\n            // bytes4(keccak256(\"unlock()\"))\n\n            // Only the old owner + recovery owner || old owner + guardian can unlock the wallet.\n\n            if (\n                signer1 != walletConfig.oldOwner ||\n                (recoveryOwners[signer2] == address(0) && guardians[signer2] == address(0))\n            ) {\n                revert LW__recoveryUnlock__invalidSignature();\n            }\n\n            // It can only be called during the time delay.\n            uint256 elapsedTime = block.timestamp - walletConfig.timestamp;\n            if (2 days < elapsedTime) revert LW__recoveryUnlock__time();\n        } else if (functionSelector == 0x0cd865ec) {\n            // bytes4(keccak256(\"recover(address)\"))\n\n            // Only the recovery owner + recovery owner || recovery owner + guardian can recover the wallet.\n            if (\n                recoveryOwners[signer1] == address(0) ||\n                (recoveryOwners[signer2] == address(0) && guardians[signer2] == address(0))\n            ) revert LW__recoveryRecover__invalidSignature();\n\n            // Can't be called once the recovery period is activated.\n            if (walletConfig.timestamp > 0) revert LW__recoveryRecover__walletLocked();\n        } else {\n            // Else, the operation is not allowed.\n            revert LW__recovery__invalidOperation();\n        }\n\n        bool success = Utils.call(address(this), 0, callData, gasleft());\n        if (!success) revert LW__recovery__callFailed();\n    }\n\n    /**\n     * @notice Returns the hash to be signed to execute a transaction.\n     */\n    function operationHash(\n        address to,\n        uint256 value,\n        bytes calldata callData,\n        uint256 _nonce\n    ) external view returns (bytes32) {\n        return keccak256(encodeOperation(to, value, callData, _nonce));\n    }\n\n    /**\n     * @notice Should return whether the signature provided is valid for the provided hash.\n     *\n     * @param hash      Hash of the data to be signed.\n     * @param signature Signature byte array associated with hash.\n     *\n     * MUST return the bytes4 magic value 0x1626ba7e when function passes.\n     * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)\n     * MUST allow external calls\n     *\n     * @return Magic value.\n     */\n    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4) {\n        address signer1 = Utils.returnSigner(hash, signature, 0);\n        address signer2 = Utils.returnSigner(hash, signature, 1);\n\n        if (signer1 != owner || (recoveryOwners[signer2] == address(0) && guardians[signer2] == address(0))) {\n            revert LaserWallet__invalidSignature();\n        }\n\n        // bytes4(keccak256(\"isValidSignature(bytes32,bytes)\")\n        return 0x1626ba7e;\n    }\n\n    /**\n     * @return chainId The chain id of this.\n     */\n    function getChainId() public view returns (uint256 chainId) {\n        return block.chainid;\n    }\n\n    /**\n     * @notice Domain separator for this wallet.\n     */\n    function domainSeparator() public view returns (bytes32) {\n        return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), address(this)));\n    }\n\n    /**\n     * @notice Encodes the transaction data.\n     */\n    function encodeOperation(\n        address to,\n        uint256 value,\n        bytes calldata callData,\n        uint256 _nonce\n    ) internal view returns (bytes memory) {\n        bytes32 opHash = keccak256(abi.encode(LASER_TYPE_STRUCTURE, to, value, keccak256(callData), _nonce));\n\n        return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), opHash);\n    }\n}\n"
    },
    "contracts/handlers/Handler.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity 0.8.16;\n\nimport \"../interfaces/IHandler.sol\";\nimport \"../interfaces/IERC165.sol\";\n\n/**\n * @title Handler\n *\n * @notice Supports token callbacks.\n */\ncontract Handler is IHandler, IERC165 {\n    function onERC721Received(\n        address,\n        address,\n        uint256,\n        bytes calldata\n    ) external pure returns (bytes4) {\n        return 0x150b7a02;\n    }\n\n    function onERC1155Received(\n        address,\n        address,\n        uint256,\n        uint256,\n        bytes calldata\n    ) external pure returns (bytes4) {\n        return 0xf23a6e61;\n    }\n\n    function onERC1155BatchReceived(\n        address,\n        address,\n        uint256[] calldata,\n        uint256[] calldata,\n        bytes calldata\n    ) external pure returns (bytes4 result) {\n        return 0xbc197c81;\n    }\n\n    function tokensReceived(\n        address,\n        address,\n        address,\n        uint256,\n        bytes calldata,\n        bytes calldata\n    ) external pure {}\n\n    function supportsInterface(bytes4 _interfaceId) external pure returns (bool) {\n        return\n            _interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.\n            _interfaceId == 0x1626ba7e || // EIP 1271.\n            _interfaceId == 0xd9b67a26 || // ERC165 interface ID for ERC1155.\n            _interfaceId == 0x4e2312e0 || // ERC-1155 `ERC1155TokenReceiver` support.\n            _interfaceId == 0xae029e0b || // Laser Wallet contract: bytes4(keccak256(\"I_AM_LASER\")).\n            _interfaceId == 0x150b7a02; // ERC721 onErc721Received.\n    }\n}\n"
    },
    "contracts/interfaces/IHandler.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.16;\n\n/**\n * @title IHandler\n *\n * @notice Has all the external functions for Handler.sol.\n */\ninterface IHandler {\n    function onERC721Received(\n        address,\n        address,\n        uint256,\n        bytes calldata\n    ) external pure returns (bytes4);\n\n    function onERC1155Received(\n        address,\n        address,\n        uint256,\n        uint256,\n        bytes calldata\n    ) external pure returns (bytes4 result);\n\n    function onERC1155BatchReceived(\n        address,\n        address,\n        uint256[] calldata,\n        uint256[] calldata,\n        bytes calldata\n    ) external pure returns (bytes4 result);\n\n    function tokensReceived(\n        address,\n        address,\n        address,\n        uint256,\n        bytes calldata,\n        bytes calldata\n    ) external pure;\n}\n"
    },
    "contracts/test/TestMigrate.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0-only\npragma solidity 0.8.16;\n\nimport \"../LaserWallet.sol\";\n\ncontract TestMigrate is LaserWallet {\n    function imNew() external pure returns (string memory) {\n        return \"New\";\n    }\n}\n"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 800
    },
    "outputSelection": {
      "*": {
        "*": [
          "storageLayout",
          "abi",
          "evm.bytecode",
          "evm.deployedBytecode",
          "evm.methodIdentifiers",
          "metadata",
          "devdoc",
          "userdoc",
          "evm.gasEstimates"
        ],
        "": [
          "ast"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}