// Contract: TimeCapsule
// Target function: function validateRecoveryAddressHash(
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;


// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
/**
 * @dev copied and condensed from @openzeppelin/contracts/proxy/utils/Initializable.sol
 **/

abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            /* changed from: (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
            to: */(isTopLevelCall && _initialized < 1) || ((address(this).code.length == 0) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized < type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     * @dev added by TPM
     */
    function isInitialized() public view returns (bool) {
        return _initialized == 1;
    }
}
library Strings {
    
    function toString(uint256 value) internal pure returns (string memory) {
        // from @openzeppelin String.sol
        unchecked {
            ////
            // uint256 length = Math.log10(value) + 1; =>
            // from @openzeppelin Math.sol
            uint256 length = 0;
            if (value >= 10**64) { value /= 10**64; length += 64; }
            if (value >= 10**32) { value /= 10**32; length += 32; }
            if (value >= 10**16) { value /= 10**16; length += 16; }
            if (value >= 10**8) { value /= 10**8; length += 8; }
            if (value >= 10**4) { value /= 10**4; length += 4; }
            if (value >= 10**2) { value /= 10**2; length += 2; }
            if (value >= 10**1) { length += 1; }
            length++;
            ////

            string memory buffer = new string(length);
            uint256 ptr;
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), "0123456789abcdef"))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }
}
/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface ITimeCapsuleFactory {

    struct OwnerCapsuleRecord {
        address capsuleAddress;
        bytes32 recoveryAddressHash;
        bool validated;
    }

    function predictedCapsuleAddress(address ownerAddress) external view returns (address predictedAddress);

    function createTimeCapsule() external;

    function createTimeCapsule(bytes32 _recoveryAddressHash) external;

    function capsuleAddressOf(address _owner) external view returns (address capsuleAddress);

    function validateRecoveryAddressHash(
        address _owner,
        bytes32 _recoveryAddressHash,
        bytes memory _signature
    ) external;

    function checkRecoveryAddress(address _owner, bytes32 _addressHash) external view returns (bool confirmed);

    function isRecoveryHashValidated(address _owner) external view returns (bool);

    function getRecoveryRecord(address _owner) external view  returns (OwnerCapsuleRecord memory);

    function recoverOwnership( address _oldOwner, address _newOwner ) external;

}
/// @dev SYNONYMS: vault, capsule, timecapsule

/// @dev _feeSplitter (feeSplitter.sol) is our own, trusted contract (context: reentrance attacks)
interface IFeeSplitter {
    function splitERC20(address tokenAddress) external returns (bool);
}

/**
 * @dev A place for TimeCapsule Constants to live
 */
abstract contract TimeCapsuleContext is Initializable {
    uint256         constant    internal    FEE_DIVISOR = 1000; // 0.1%
    uint64          constant    internal    ONE_HOUR =  60 * 60;
    uint64          constant    internal    ONE_DAY =  24 * 60 * 60;
    uint64          constant    internal    SEVEN_DAYS = 7 * ONE_DAY;
    uint64          constant    internal    ONE_YEAR = 365 * ONE_DAY;
    uint64          constant    internal    MAX_LOCK_SECONDS = 20089 * ONE_DAY; // ~55 years (365.25 days each)
    uint64          constant    internal    TIMELOCK_SECONDS = SEVEN_DAYS;
    uint64          constant    internal    BEHAVIORLOCK_SECONDS = ONE_YEAR;
    address         constant    internal    NATIVE_COIN = address(0);
    address         constant    internal    INVALID_SIGNER_ADDRESS = address(0);
    string          constant    internal    VAULT_RECOVERY_AUTHORIZATION="VAULT RECOVERY AUTHORIZATION";
    string          constant    internal    CONFIRMING_RECOVERY_ADDRESS="CONFIRMING RECOVERY ADDRESS";

    enum VaultStatus {
        NOMINAL,    // all is well
        PANIC,      // 'panic switch' activated (lockout for recovery)
        RECOVERED   // (read-only)
    }

    enum LockState {
        INERT,
        WITHDRAWAL_PENDING
    }

    enum LockType {
        DRIP,
        HARD,
        BEHAVIORAL
    }

    struct Lock {
        LockState state;            // uint8
        bytes32 tag;                // <= 31 character descriptor string
        uint64 lockTime;            // UTC
        uint64 unlockTime;          // UTC
        uint64 releaseTime;         // UTC timestamp when time-locked funds release for sending
        LockType lockType;          // uint8
        bytes6 unlockHash;          // first 6 bytes of keccak256 hash
        // 256 bit boundary
        uint256 lockedAmount;       // in ERC20 token's base units
        uint256 withdrawnToDate;    // in ERC20 token's base units
        uint256 timelockedAmount;   // amount of any pending, time-locked withdrawal
    }
}

/**
 * @title TimeCapsule
 */
contract TimeCapsule is TimeCapsuleContext{

    address public owner;
    ITimeCapsuleFactory private _capsuleFactory;

    mapping(address => Lock[])  private _locks; // address is 'rc20 token contract address or address(0)
    mapping(address => uint256) private _totalLockedAmount;

    uint64 private _deadmanTime;

    VaultStatus private _vaultStatus;

    IFeeSplitter private _feeSplitter;

    constructor() {
        // Contructor not used by factory. We use it here only to prevent master copy hijacks
        // the factory will call this.initialize() for one-off setup tasks.
        _disableInitializers();
    }

    /**
     * @dev Because this contract is a proxied clone (oppenzep'Clone.sol)
     *      functions herein get called **twice**. The first call has `msg.sender`
     *      set as the address of the caller, as usual. The second _proxy_ call
     *      has `msg.sender` set to this contract's own address. Hence, we roll
     *      our own 'Ownable' (minus transferability) and with a different name
     *      than 'onlyOwner' to avoid confusion.
     */
    modifier _onlyCapsuleOwner() {
        if (address(this) != msg.sender) {
            require(owner == msg.sender, "Not owner");
            _;
        }
    }

    /**
     * @dev This modifier blocks calls to functions when the vault is in the 'panic' state.
     */
    modifier _notPanicState() {
        if (address(this) != msg.sender) {
            require(_vaultStatus != VaultStatus.PANIC, "Forbidden");
            _;
        }
    }

    /**
     * Called once by factory directly after cloning to initialize vault.
     * @param _newOwner the vaults's owner address
     * @param _factoryAddress used to manage recovery from panic state aka "undo a hack"
     * @param _feeSplitterContract the [contract] address to send fees
     */
    function initialize(
        address _newOwner,
        address _factoryAddress,
        IFeeSplitter _feeSplitterContract
    )
        initializer
        public
    {
        owner = _newOwner;
        _capsuleFactory = ITimeCapsuleFactory(_factoryAddress);
        _deadmanTime = (uint64)(block.timestamp);
        _vaultStatus = VaultStatus.NOMINAL;
        _feeSplitter = _feeSplitterContract;
    }

    /**
     * When native coin is sent directly to this vault (as if it were a standard wallet)
     * the funds are auto-locked for seven days (and the fee is paid accordingly.) This is
     * intended as a safety feature and not to be advertized as good practice.
     */
    receive() external payable { }

    event Locked(
        bytes32 tag,
        address tokenAddress,
        uint256 lockIndex,
        uint256 amount,
        uint256 fee,
        uint256 lockTime,
        uint256 unlockTime,
        LockType lockType
    );

    event WithdrawalInitiated(
        address tokenAddress,
        uint256 lockIndex,
        uint256 amount
    );

    event WithdrawalCancelled(
        address tokenAddress,
        uint256 lockIndex
    );

    event Withdrawal(
        address tokenAddress,
        uint256 lockIndex,
        uint256 amount
    );

    event LockReleased(
        address tokenAddress,
        uint256 lockIndex
    );

    event RecoveryInitiated();

    event Recovered();

    error HardLocked(
        uint64 unlockTime
    );

    error InsufficientFunds(
        uint256 grossBalance,
        uint256 alreadyLocked,
        uint256 availableToLock
    );

    /**
     * Transfers nativer coin from the vault to a recipient.
     * @notice Do not use simple trasfer(<maxgas: 2300!>) — Gnosis safe (for example) infamously incompatible
     * @param _to recipient address
     * @param _value base unit amount to transfer
     */
    function _transferNative(
        address _to,
        uint256 _value
    )
        internal
        returns (bool success)
    {
        (success, ) = _to.call{ value: _value }("");
    }

    /**
     * Transfers 'RC20 tokens from the vault to a recipient.
     * @notice internal function
     * @param _tokenAddress 'RC20 token contract address
     * @param _to recipient address
     * @param _value base unit amount to transfer
     * @return bool
     */
    function _transferToken(
        address _tokenAddress,
        address _to,
        uint256 _value
    )
        internal
        returns (bool)
    {
        return IERC20(_tokenAddress).transfer(
            _to,
            _value
        );
    }

    /**
     * Returns the current "state" of the vault. See `enum VaultStatus {...}`
     * @return status enum VaultStatus.<NOMINAL|PANIC|RECOVERED>
     */
    function vaultStatus()
        public
        view
        returns (VaultStatus status)
    {
        status = _vaultStatus;
    }