contract MixinSignatureValidator is
    ReentrancyGuard,
    MSignatureValidator,
    MTransactions
{
    using LibBytes for bytes;

    // Mapping of hash => signer => signed
    mapping (bytes32 => mapping (address => bool)) public preSigned;

    // Mapping of signer => validator => approved
    mapping (address => mapping (address => bool)) public allowedValidators;

    /// @dev Approves a hash on-chain using any valid signature type.
    ///      After presigning a hash, the preSign signature type will become valid for that hash and signer.
    /// @param signerAddress Address that should have signed the given hash.
    /// @param signature Proof that the hash has been signed by signer.
    function preSign(
        bytes32 hash,
        address signerAddress,
        bytes calldata signature
    )
        external
    {
        if (signerAddress != msg.sender) {
            require(
                isValidSignature(
                    hash,
                    signerAddress,
                    signature
                ),
                "INVALID_SIGNATURE"
            );
        }
        preSigned[hash][signerAddress] = true;
    }

    /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf.
    /// @param validatorAddress Address of Validator contract.
    /// @param approval Approval or disapproval of  Validator contract.
    function setSignatureValidatorApproval(
        address validatorAddress,
        bool approval
    )
        external
        nonReentrant
    {
        address signerAddress = getCurrentContextAddress();
        allowedValidators[signerAddress][validatorAddress] = approval;
        emit SignatureValidatorApproval(
            signerAddress,
            validatorAddress,
            approval
        );
    }

    /// @dev Verifies that a hash has been signed by the given signer.
    /// @param hash Any 32 byte hash.
    /// @param signerAddress Address that should have signed the given hash.
    /// @param signature Proof that the hash has been signed by signer.
    /// @return True if the address recovered from the provided signature matches the input signer address.
    function isValidSignature(
        bytes32 hash,
        address signerAddress,
        bytes memory signature
    )
        public
        view
        returns (bool isValid)
    {
        require(
            signature.length > 0,
            "LENGTH_GREATER_THAN_0_REQUIRED"
        );

        // Pop last byte off of signature byte array.
        uint8 signatureTypeRaw = uint8(signature.popLastByte());

        // Ensure signature is supported
        require(
            signatureTypeRaw < uint8(SignatureType.NSignatureTypes),
            "SIGNATURE_UNSUPPORTED"
        );

        SignatureType signatureType = SignatureType(signatureTypeRaw);

        // Variables are not scoped in Solidity.
        uint8 v;
        bytes32 r;
        bytes32 s;
        address recovered;

        // Always illegal signature.
        // This is always an implicit option since a signer can create a
        // signature array with invalid type or length. We may as well make
        // it an explicit option. This aids testing and analysis. It is
        // also the initialization value for the enum type.
        if (signatureType == SignatureType.Illegal) {
            revert("SIGNATURE_ILLEGAL");

        // Always invalid signature.
        // Like Illegal, this is always implicitly available and therefore
        // offered explicitly. It can be implicitly created by providing
        // a correctly formatted but incorrect signature.
        } else if (signatureType == SignatureType.Invalid) {
            require(
                signature.length == 0,
                "LENGTH_0_REQUIRED"
            );
            isValid = false;
            return isValid;

        // Signature using EIP712
        } else if (signatureType == SignatureType.EIP712) {
            require(
                signature.length == 65,
                "LENGTH_65_REQUIRED"
            );
            v = uint8(signature[0]);
            r = signature.readBytes32(1);
            s = signature.readBytes32(33);
            recovered = ecrecover(
                hash,
                v,
                r,
                s
            );
            isValid = signerAddress == recovered;
            return isValid;

        // Signed using web3.eth_sign
        } else if (signatureType == SignatureType.EthSign) {
            require(
                signature.length == 65,
                "LENGTH_65_REQUIRED"
            );
            v = uint8(signature[0]);
            r = signature.readBytes32(1);
            s = signature.readBytes32(33);
            recovered = ecrecover(
                keccak256(abi.encodePacked(
                    "\x19Ethereum Signed Message:\n32",
                    hash
                )),
                v,
                r,
                s
            );
            isValid = signerAddress == recovered;
            return isValid;

        // Signature verified by wallet contract.
        // If used with an order, the maker of the order is the wallet contract.
        } else if (signatureType == SignatureType.Wallet) {
            isValid = isValidWalletSignature(
                hash,
                signerAddress,
                signature
            );
            return isValid;

        // Signature verified by validator contract.
        // If used with an order, the maker of the order can still be an EOA.
        // A signature using this type should be encoded as:
        // | Offset   | Length | Contents                        |
        // | 0x00     | x      | Signature to validate           |
        // | 0x00 + x | 20     | Address of validator contract   |
        // | 0x14 + x | 1      | Signature type is always "\x06" |
        } else if (signatureType == SignatureType.Validator) {
            // Pop last 20 bytes off of signature byte array.
            uint256 signatureLength = signature.length;
            address validatorAddress = signature.readAddress(signatureLength - 20);

            // Ensure signer has approved validator.
            if (!allowedValidators[signerAddress][validatorAddress]) {
                return false;
            }
            isValid = isValidValidatorSignature(
                validatorAddress,
                hash,
                signerAddress,
                signature
            );
            return isValid;

        // Signer signed hash previously using the preSign function.
        } else if (signatureType == SignatureType.PreSigned) {
            isValid = preSigned[hash][signerAddress];
            return isValid;
        }

        // Anything else is illegal (We do not return false because
        // the signature may actually be valid, just not in a format
        // that we currently support. In this case returning false
        // may lead the caller to incorrectly believe that the
        // signature was invalid.)
        revert("SIGNATURE_UNSUPPORTED");
    }

    /// @dev Verifies signature using logic defined by Wallet contract. Wallet contract
    ///      must return `bytes4(keccak256("isValidWalletSignature(bytes32,address,bytes)"))`
    ///      miime:   or `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`
    ///      miime:   or `bytes4(keccak256("isValidSignature(bytes,bytes)")`
    /// @param hash Any 32 byte hash.
    /// @param walletAddress Address that should have signed the given hash
    ///                      and defines its own signature verification method.
    /// @param signature Proof that the hash has been signed by signer.
    /// @return True if signature is valid for given wallet..
    function isValidWalletSignature(
        bytes32 hash,
        address walletAddress,
        bytes memory signature
    )
        internal
        view
        returns (bool isValid)
    {
        bytes memory callData = abi.encodeWithSelector(
            IWallet(walletAddress).isValidSignature.selector,
            hash,
            signature
        );
        // bytes4 0xb0671381
        bytes32 magicValue = bytes32(bytes4(keccak256("isValidWalletSignature(bytes32,address,bytes)")));
        // miime: bytes4 0x20c13b0b for EIP-1271
        bytes32 magicValueEIP1271 = bytes32(bytes4(keccak256("isValidSignature(bytes,bytes)")));
        // miime: bytes4 0x1626ba7e for Dapper contract wallet https://github.com/dapperlabs/dapper-contracts/blob/2ccb26e/contracts/ERC1271/ERC1271.sol#L6
        bytes32 magicValueEIP1271Old = bytes32(bytes4(keccak256("isValidSignature(bytes32,bytes)")));
        assembly {
            // extcodesize added as an extra safety measure
            if iszero(extcodesize(walletAddress)) {
                // Revert with `Error("WALLET_ERROR")`
                mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
                mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000)
                mstore(96, 0)
                revert(0, 100)
            }

            let cdStart := add(callData, 32)
            let success := staticcall(
                gas,              // forward all gas
                walletAddress,    // address of Wallet contract
                cdStart,          // pointer to start of input
                mload(callData),  // length of input
                cdStart,          // write output over input
                32                // output size is 32 bytes
            )

            if iszero(eq(returndatasize(), 32)) {
                // Revert with `Error("WALLET_ERROR")`
                mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
                mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000)
                mstore(96, 0)
                revert(0, 100)
            }

            switch success
            case 0 {
                // Revert with `Error("WALLET_ERROR")`
                mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
                mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000)
                mstore(96, 0)
                revert(0, 100)
            }
            case 1 {
                // miime: Added EIP1271
                // Signature is valid if call did not revert and returned true
                isValid := or(
                    eq(
                        and(mload(cdStart), 0xffffffff00000000000000000000000000000000000000000000000000000000),
                        and(magicValue, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                    ),
                    or(
                        eq(
                            and(mload(cdStart), 0xffffffff00000000000000000000000000000000000000000000000000000000),
                            and(magicValueEIP1271, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                        ),
                        eq(
                            and(mload(cdStart), 0xffffffff00000000000000000000000000000000000000000000000000000000),
                            and(magicValueEIP1271Old, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                        )
                    )
                )
            }
        }
        return isValid;
    }

    /// @dev Verifies signature using logic defined by Validator contract.
    ///      Validator must return `bytes4(keccak256("isValidValidatorSignature(address,bytes32,address,bytes)"))`
    /// @param validatorAddress Address of validator contract.
    /// @param hash Any 32 byte hash.
    /// @param signerAddress Address that should have signed the given hash.
    /// @param signature Proof that the hash has been signed by signer.
    /// @return True if the address recovered from the provided signature matches the input signer address.
    function isValidValidatorSignature(
        address validatorAddress,
        bytes32 hash,
        address signerAddress,
        bytes memory signature
    )
        internal
        view
        returns (bool isValid)
    {
        bytes memory callData = abi.encodeWithSelector(
            IValidator(signerAddress).isValidSignature.selector,
            hash,
            signerAddress,
            signature
        );
        // bytes4 0x42b38674
        bytes32 magicValue = bytes32(bytes4(keccak256("isValidValidatorSignature(address,bytes32,address,bytes)")));
        assembly {
            // extcodesize added as an extra safety measure
            if iszero(extcodesize(validatorAddress)) {
                // Revert with `Error("VALIDATOR_ERROR")`
                mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
                mstore(64, 0x0000000f56414c494441544f525f4552524f5200000000000000000000000000)
                mstore(96, 0)
                revert(0, 100)
            }

            let cdStart := add(callData, 32)
            let success := staticcall(
                gas,               // forward all gas
                validatorAddress,  // address of Validator contract
                cdStart,           // pointer to start of input
                mload(callData),   // length of input
                cdStart,           // write output over input
                32                 // output size is 32 bytes
            )

            if iszero(eq(returndatasize(), 32)) {
                // Revert with `Error("VALIDATOR_ERROR")`
                mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
                mstore(64, 0x0000000f56414c494441544f525f4552524f5200000000000000000000000000)
                mstore(96, 0)
                revert(0, 100)
            }

            switch success
            case 0 {
                // Revert with `Error("VALIDATOR_ERROR")`
                mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
                mstore(64, 0x0000000f56414c494441544f525f4552524f5200000000000000000000000000)
                mstore(96, 0)
                revert(0, 100)
            }
            case 1 {
                // Signature is valid if call did not revert and returned true
                isValid := eq(
                    and(mload(cdStart), 0xffffffff00000000000000000000000000000000000000000000000000000000),
                    and(magicValue, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                )
            }
        }
        return isValid;
    }
}
function isValidWalletSignature(
    bytes32 hash,
    address walletAddress,
    bytes memory signature
)
    internal
    view
    returns (bool isValid)
{
    bytes memory callData = abi.encodeWithSelector(
        IWallet(walletAddress).isValidSignature.selector,
        hash,
        signature
    );
    // bytes4 0xb0671381
    bytes32 magicValue = bytes32(bytes4(keccak256("isValidWalletSignature(bytes32,address,bytes)")));
    // miime: bytes4 0x20c13b0b for EIP-1271
    bytes32 magicValueEIP1271 = bytes32(bytes4(keccak256("isValidSignature(bytes,bytes)")));
    // miime: bytes4 0x1626ba7e for Dapper contract wallet https://github.com/dapperlabs/dapper-contracts/blob/2ccb26e/contracts/ERC1271/ERC1271.sol#L6
    bytes32 magicValueEIP1271Old = bytes32(bytes4(keccak256("isValidSignature(bytes32,bytes)")));
    assembly {
        // extcodesize added as an extra safety measure
        if iszero(extcodesize(walletAddress)) {
            // Revert with `Error("WALLET_ERROR")`
            mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
            mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
            mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000)
            mstore(96, 0)
            revert(0, 100)
        }

        let cdStart := add(callData, 32)
        let success := staticcall(
            gas,              // forward all gas
            walletAddress,    // address of Wallet contract
            cdStart,          // pointer to start of input
            mload(callData),  // length of input
            cdStart,          // write output over input
            32                // output size is 32 bytes
        )

        if iszero(eq(returndatasize(), 32)) {
            // Revert with `Error("WALLET_ERROR")`
            mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
            mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
            mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000)
            mstore(96, 0)
            revert(0, 100)
        }

        switch success
        case 0 {
            // Revert with `Error("WALLET_ERROR")`
            mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
            mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
            mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000)
            mstore(96, 0)
            revert(0, 100)
        }
        case 1 {
            // miime: Added EIP1271
            // Signature is valid if call did not revert and returned true
            isValid := or(
                eq(
                    and(mload(cdStart), 0xffffffff00000000000000000000000000000000000000000000000000000000),
                    and(magicValue, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                ),
                or(
                    eq(
                        and(mload(cdStart), 0xffffffff00000000000000000000000000000000000000000000000000000000),
                        and(magicValueEIP1271, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                    ),
                    eq(
                        and(mload(cdStart), 0xffffffff00000000000000000000000000000000000000000000000000000000),
                        and(magicValueEIP1271Old, 0xffffffff00000000000000000000000000000000000000000000000000000000)
                    )
                )
            )
        }
    }
    return isValid;
}

/// @dev Verifies signature using logic defined by Wallet contract. Wallet contract
///      must return `bytes4(keccak256("isValidWalletSignature(bytes32,address,bytes)"))`
///      miime:   or `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`
///      miime:   or `bytes4(keccak256("isValidSignature(bytes,bytes)")`
/// @param hash Any 32 byte hash.
/// @param walletAddress Address that should have signed the given hash
///                      and defines its own signature verification method.
/// @param signature Proof that the hash has been signed by signer.
/// @return True if signature is valid for given wallet..