ETH Price: $2,928.36 (-1.03%)
 

Overview

ETH Balance

0 ETH

ETH Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To

There are no matching entries

1 Internal Transaction found.

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To
1439102202025-11-17 22:40:1768 days ago1763419217  Contract Creation0 ETH

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
HookExecutor

Compiler Version
v0.8.30+commit.73712a01

Optimization Enabled:
Yes with 200 runs

Other Settings:
prague EvmVersion
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

import {IHookExecutor, Hook} from "../interfaces/IHookExecutor.sol";
import {CallWithExactGas} from "../libraries/CallWithExactGas.sol";

/// @dev A contract for executing user-specified hooks. It ensures that
/// user-specified calls are not executed from a privileged context, and that
/// reverts do not prevent Router and borrower matching from executing.
contract HookExecutor is IHookExecutor {
    using CallWithExactGas for bytes;

    /// @dev The address of the Router contract.
    address public immutable router;
    /// @notice Gas amount reserved for the exact EXTCODESIZE call and additional overhead
    ///         required by the `CallWithExactGas` library to safely execute calls.
    /// @dev This value should be carefully calibrated based on EVM gas costs to
    ///      prevent unexpected failures when making external calls with precise gas.
    ///      It can be updated by the Router contract if needed.
    uint32 public gasForCallExactCheck = 5_000;

    /// @dev Emitted after each hook is executed.
    event HookExecuted(address indexed target, bool success);
    /// @dev Emitted when execution of a hook fails and failure is not allowed.
    event HookExecutionFailed(address indexed target, bool success);
    /// @dev Emitted when the gasForCallExactCheck is set / updated.
    event GasForCallExactCheckSet(uint32 gasForCallExactCheck);

    /// @dev Error indicating that the contract was not called from the Router contract.
    error NotRouter();
    error HookExecutionFailedError(address target);
    error ZeroGasForCallExactCheck();

    /// @param router_ The address of the Router contract.
    constructor(address router_) {
        router = router_;
    }

    /// @dev Modifier that ensures that the `msg.sender` is the Router contract.
    modifier onlyRouter() {
        _onlyRouter();
        _;
    }

    /// @dev Executes the user specified hooks. Called only by the Router contract.
    /// Each hook is executed with the specified gas limit, and failure does not revert the entire transaction.
    /// Each hook is only executed before the specified expiry timestamp.
    /// @param hooks The hooks to execute.
    function execute(Hook[] calldata hooks) external onlyRouter {
        unchecked {
            for (uint256 i = 0; i < hooks.length; ++i) {
                Hook calldata hook = hooks[i];

                (bool success,) = hook.callData
                    ._callWithExactGasEvenIfTargetIsNoContract(hook.target, hook.gasLimit, gasForCallExactCheck);

                if (!success) {
                    // Revert if the call fails
                    revert HookExecutionFailedError(hook.target);
                } else {
                    emit HookExecuted(hook.target, success);
                }
            }
        }
    }

    /// @notice Updates the gas reserved for the exact EXTCODESIZE call and related checks
    ///         used internally when executing hooks.
    /// @dev Only callable by the Router contract.
    /// @param gasForCallExactCheck_ The new gas amount to reserve for the exact call check.
    ///        Should be calibrated according to current gas costs of the EXTCODESIZE opcode
    ///        and overhead from the CallWithExactGas library.
    function setGasForCallExactCheck(uint32 gasForCallExactCheck_) external onlyRouter {
        require(gasForCallExactCheck_ > 0, ZeroGasForCallExactCheck());
        gasForCallExactCheck = gasForCallExactCheck_;
        emit GasForCallExactCheckSet(gasForCallExactCheck);
    }

    function _onlyRouter() internal view {
        require(msg.sender == router, NotRouter());
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

/// @dev A user-specified hook to be executed before or after an intent.
/// @param target The address of the contract to call for the hook.
/// @param callData The calldata to be sent to the target contract for the hook execution.
/// @param gasLimit The maximum gas allowed for the hook execution.
struct Hook {
    address target;
    bytes callData;
    uint256 gasLimit;
}

/// @notice Interface for executing an array of hooks.
interface IHookExecutor {
    /// @notice Executes the provided hooks.
    /// @param hooks The array of Hook structs to execute.
    function execute(Hook[] calldata hooks) external;

    /// @notice Sets the gas limit for exact call checks.
    /// @param gasForCallExactCheck_ The new gas limit value.
    function setGasForCallExactCheck(uint32 gasForCallExactCheck_) external;

    /// @notice Returns the current gas limit for exact call checks.
    /// @return The gas limit value.
    function gasForCallExactCheck() external view returns (uint32);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

/// @title CallWithExactGas Library
/// @notice This library provides multiple `callWithExactGas` functions that are resistant to gas bomb attacks.
/// @dev Assembly code is duplicated intentionally to ensure safety and maintain the structure within inline assembly blocks.
/// @notice Adapted from Chainlink: https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/shared/call/CallWithExactGas.sol
library CallWithExactGas {
    bytes4 internal constant NO_CONTRACT_SIG = 0x0c3b563c;
    bytes4 internal constant NO_GAS_FOR_CALL_EXACT_CHECK_SIG = 0xafa32a2c;
    bytes4 internal constant NOT_ENOUGH_GAS_FOR_CALL_SIG = 0x37c3be29;

    /// @notice calls target address with exactly gasAmount gas and payload as calldata.
    /// Accounts for gasForCallExactCheck gas that will be used by this function. Will revert
    /// if the target is not a contact. Will revert when there is not enough gas to call the
    /// target with gasAmount gas.
    /// @dev Ignores the return data, which makes it immune to gas bomb attacks.
    /// @return success whether the call succeeded
    function _callWithExactGas(bytes memory payload, address target, uint256 gasLimit, uint32 gasForCallExactCheck)
        internal
        returns (bool success)
    {
        assembly {
            // solidity calls check that a contract actually exists at the destination, so we do the same
            // Note we do this check prior to measuring gas so gasForCallExactCheck (our "cushion")
            // doesn't need to account for it.
            if iszero(extcodesize(target)) {
                mstore(0x0, NO_CONTRACT_SIG)
                revert(0x0, 0x4)
            }

            let g := gas()
            // Compute g -= gasForCallExactCheck and check for underflow
            // The gas actually passed to the callee is _min(gasAmount, 63//64*gas available).
            // We want to ensure that we revert if gasAmount >  63//64*gas available
            // as we do not want to provide them with less, however that check itself costs
            // gas. gasForCallExactCheck ensures we have at least enough gas to be able
            // to revert if gasAmount >  63//64*gas available.
            if lt(g, gasForCallExactCheck) {
                mstore(0x0, NO_GAS_FOR_CALL_EXACT_CHECK_SIG)
                revert(0x0, 0x4)
            }
            g := sub(g, gasForCallExactCheck)
            // if g - g//64 <= gasAmount, revert. We subtract g//64 because of EIP-150
            if iszero(gt(sub(g, div(g, 64)), gasLimit)) {
                mstore(0x0, NOT_ENOUGH_GAS_FOR_CALL_SIG)
                revert(0x0, 0x4)
            }

            // call and return whether we succeeded. ignore return data
            // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength)
            success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0x0, 0x0)
        }
        return success;
    }

    /// @notice calls target address with exactly gasAmount gas and payload as calldata.
    /// Account for gasForCallExactCheck gas that will be used by this function. Will revert
    /// if the target is not a contact. Will revert when there is not enough gas to call the
    /// target with gasAmount gas.
    /// @dev Caps the return data length, which makes it immune to gas bomb attacks.
    /// @dev Return data cap logic borrowed from
    /// https://github.com/nomad-xyz/ExcessivelySafeCall/blob/main/src/ExcessivelySafeCall.sol.
    /// @return success whether the call succeeded
    /// @return retData the return data from the call, capped at maxReturnBytes bytes
    /// @return gasUsed the gas used by the external call. Does not include the overhead of this function.
    function _callWithExactGasSafeReturnData(
        bytes memory payload,
        address target,
        uint256 gasLimit,
        uint32 gasForCallExactCheck,
        uint16 maxReturnBytes
    ) internal returns (bool success, bytes memory retData, uint256 gasUsed) {
        // allocate retData memory ahead of time
        retData = new bytes(maxReturnBytes);

        assembly {
            // solidity calls check that a contract actually exists at the destination, so we do the same
            // Note we do this check prior to measuring gas so gasForCallExactCheck (our "cushion")
            // doesn't need to account for it.
            if iszero(extcodesize(target)) {
                mstore(0x0, NO_CONTRACT_SIG)
                revert(0x0, 0x4)
            }

            let g := gas()
            // Compute g -= gasForCallExactCheck and check for underflow
            // The gas actually passed to the callee is _min(gasAmount, 63//64*gas available).
            // We want to ensure that we revert if gasAmount >  63//64*gas available
            // as we do not want to provide them with less, however that check itself costs
            // gas. gasForCallExactCheck ensures we have at least enough gas to be able
            // to revert if gasAmount >  63//64*gas available.
            if lt(g, gasForCallExactCheck) {
                mstore(0x0, NO_GAS_FOR_CALL_EXACT_CHECK_SIG)
                revert(0x0, 0x4)
            }
            g := sub(g, gasForCallExactCheck)
            // if g - g//64 <= gasAmount, revert. We subtract g//64 because of EIP-150
            if iszero(gt(sub(g, div(g, 64)), gasLimit)) {
                mstore(0x0, NOT_ENOUGH_GAS_FOR_CALL_SIG)
                revert(0x0, 0x4)
            }

            // We save the gas before the call so we can calculate how much gas the call used
            let gasBeforeCall := gas()
            // call and return whether we succeeded. ignore return data
            // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength)
            success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0x0, 0x0)
            gasUsed := sub(gasBeforeCall, gas())

            // limit our copy to maxReturnBytes bytes
            let toCopy := returndatasize()
            if gt(toCopy, maxReturnBytes) { toCopy := maxReturnBytes }
            // Store the length of the copied bytes
            mstore(retData, toCopy)
            // copy the bytes from retData[0:_toCopy]
            returndatacopy(add(retData, 0x20), 0x0, toCopy)
        }
        return (success, retData, gasUsed);
    }

    /// @notice Calls target address with exactly gasAmount gas and payload as calldata
    /// or reverts if at least gasLimit gas is not available.
    /// @dev Does not check if target is a contract. If it is not a contract, the low-level
    /// call will still be made and it will succeed.
    /// @dev Ignores the return data, which makes it immune to gas bomb attacks.
    /// @return success whether the call succeeded
    /// @return sufficientGas Whether there was enough gas to make the call
    function _callWithExactGasEvenIfTargetIsNoContract(
        bytes memory payload,
        address target,
        uint256 gasLimit,
        uint32 gasForCallExactCheck
    ) internal returns (bool success, bool sufficientGas) {
        assembly {
            let g := gas()
            // Compute g -= CALL_WITH_EXACT_GAS_CUSHION and check for underflow. We
            // need the cushion since the logic following the above call to gas also
            // costs gas which we cannot account for exactly. So cushion is a
            // conservative upper bound for the cost of this logic.
            if iszero(lt(g, gasForCallExactCheck)) {
                g := sub(g, gasForCallExactCheck)
                // If g - g//64 <= gasAmount, we don't have enough gas. We subtract g//64 because of EIP-150.
                if gt(sub(g, div(g, 64)), gasLimit) {
                    // Call and ignore success/return data. Note that we did not check
                    // whether a contract actually exists at the target address.
                    success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0x0, 0x0)
                    sufficientGas := true
                }
            }
        }
        return (success, sufficientGas);
    }
}

Settings
{
  "remappings": [
    "@openzeppelin/contracts-upgradeable/=dependencies/@openzeppelin-contracts-upgradeable-5.4.0/",
    "@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-5.4.0/",
    "blocklock-solidity/=dependencies/blocklock-solidity-0.2.0/src/",
    "bls-solidity-0.3.0/=dependencies/bls-solidity-0.3.0/",
    "bls-solidity/=dependencies/bls-solidity-0.3.0/src/",
    "forge-std/=dependencies/forge-std-1.10.0/src/",
    "onlyswaps-solidity/=dependencies/onlyswaps-solidity-0.7.0/src/",
    "randomness-solidity/=dependencies/randomness-solidity-0.4.0/src/",
    "solmate/=dependencies/solmate-89365b880c4f3c786bdd453d4b8e8fe410344a69/",
    "uniswap-permit2-unlocked/=dependencies/uniswap-permit2-unlocked-1.0.0/src/",
    "uniswap-permit2/=dependencies/uniswap-permit2-0x000000000022D473030F116dDEE9F6B43aC78BA3/src/",
    "@ds-test/=dependencies/blocklock-solidity-0.2.0/lib/forge-std/lib/ds-test/src/",
    "@openzeppelin-contracts-5.4.0/=dependencies/@openzeppelin-contracts-5.4.0/",
    "@openzeppelin-contracts-upgradeable-5.4.0/=dependencies/@openzeppelin-contracts-upgradeable-5.4.0/",
    "blocklock-solidity-0.2.0/=dependencies/blocklock-solidity-0.2.0/src/",
    "chainlink-2.24.0/=dependencies/randomness-solidity-0.2.0/dependencies/chainlink-2.24.0/",
    "ds-test/=dependencies/uniswap-permit2-unlocked-1.0.0/lib/solmate/lib/ds-test/src/",
    "forge-gas-snapshot/=dependencies/uniswap-permit2-unlocked-1.0.0/lib/forge-gas-snapshot/src/",
    "forge-std-1.10.0/=dependencies/forge-std-1.10.0/src/",
    "onlyswaps-solidity-0.1.0/=dependencies/onlyswaps-solidity-0.1.0/src/",
    "onlyswaps-solidity-0.2.0/=dependencies/onlyswaps-solidity-0.2.0/src/",
    "onlyswaps-solidity-0.3.0/=dependencies/onlyswaps-solidity-0.3.0/src/",
    "onlyswaps-solidity-0.4.0/=dependencies/onlyswaps-solidity-0.4.0/src/",
    "onlyswaps-solidity-0.5.0/=dependencies/onlyswaps-solidity-0.5.0/src/",
    "onlyswaps-solidity-0.7.0/=dependencies/onlyswaps-solidity-0.7.0/src/",
    "openzeppelin-contracts/=dependencies/uniswap-permit2-unlocked-1.0.0/lib/openzeppelin-contracts/",
    "randomness-solidity-0.1.0/=dependencies/randomness-solidity-0.1.0/src/",
    "randomness-solidity-0.2.0/=dependencies/randomness-solidity-0.2.0/src/",
    "randomness-solidity-0.3.0/=dependencies/randomness-solidity-0.3.0/src/",
    "randomness-solidity-0.4.0/=dependencies/randomness-solidity-0.4.0/src/",
    "solmate-89365b880c4f3c786bdd453d4b8e8fe410344a69/=dependencies/solmate-89365b880c4f3c786bdd453d4b8e8fe410344a69/src/",
    "uniswap-permit2-0x000000000022D473030F116dDEE9F6B43aC78BA3/=dependencies/uniswap-permit2-0x000000000022D473030F116dDEE9F6B43aC78BA3/src/",
    "uniswap-permit2-unlocked-1.0.0/=dependencies/uniswap-permit2-unlocked-1.0.0/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "prague",
  "viaIR": true
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"router_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"HookExecutionFailedError","type":"error"},{"inputs":[],"name":"NotRouter","type":"error"},{"inputs":[],"name":"ZeroGasForCallExactCheck","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"gasForCallExactCheck","type":"uint32"}],"name":"GasForCallExactCheckSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"}],"name":"HookExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"}],"name":"HookExecutionFailed","type":"event"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"uint256","name":"gasLimit","type":"uint256"}],"internalType":"struct Hook[]","name":"hooks","type":"tuple[]"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gasForCallExactCheck","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"gasForCallExactCheck_","type":"uint32"}],"name":"setGasForCallExactCheck","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60a034607c57601f6104cb38819003918201601f19168301916001600160401b03831184841017608057808492602094604052833981010312607c57516001600160a01b0381168103607c5761138863ffffffff195f5416175f556080526040516104369081610095823960805181818161016701526103720152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c8063760f2a0b14610054578063a8afbddd1461004f578063b066aafc1461004a5763f887ea4014610045575f80fd5b610152565b610130565b6100b7565b346100b35760203660031901126100b35760043567ffffffffffffffff81116100b357366023820112156100b357806004013567ffffffffffffffff81116100b3573660248260051b840101116100b35760246100b19201610196565b005b5f80fd5b346100b35760203660031901126100b35760043563ffffffff81168091036100b3576100e1610370565b8015610121576020817f75de08d29c16281a600d57180e47c4ad749f743264f4bf5670477b14871361989263ffffffff195f5416175f55604051908152a1005b6305691de360e41b5f5260045ffd5b346100b3575f3660031901126100b357602063ffffffff5f5416604051908152f35b346100b3575f3660031901126100b3576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b61019e610370565b5f915b8083106101ad57505050565b6101bb838284959694610282565b936101f96101d66101cf60208801886102b8565b36916102ff565b6101df8761035c565b6040880135906101f35f5463ffffffff1690565b926103b1565b50806102295761022661020b8761035c565b630b8b4c9960e11b5f526001600160a01b0316600452602490565b5ffd5b7fa5ce1292f71836359bea068ac79cd2424ca8f795c7421606753cb7bcbee5f3db610276600194959697610262868060a01b039161035c565b604051941515855216929081906020820190565b0390a2019192906101a1565b91908110156102a45760051b81013590605e19813603018212156100b3570190565b634e487b7160e01b5f52603260045260245ffd5b903590601e19813603018212156100b3570180359067ffffffffffffffff82116100b3576020019181360383136100b357565b634e487b7160e01b5f52604160045260245ffd5b92919267ffffffffffffffff82116103575760405191601f8101601f19908116603f0116830167ffffffffffffffff811184821017610357576040528294818452818301116100b3578281602093845f960137010152565b6102eb565b356001600160a01b03811681036100b35790565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036103a257565b639165520160e01b5f5260045ffd5b5f949385939192905a90808210156103cd575b50505050509190565b8391038060061c9003116103e3575b80806103c4565b5f949550849350839060208451940192f1906001915f80806103dc56fea26469706673582212204d12376dceb1947c84ce8f839c52cf765b533839d2b6a0764451befbc5f8686764736f6c634300081e003300000000000000000000000016323707e61d20a39aae5ab64808e480b91658ab

Deployed Bytecode

0x60806040526004361015610011575f80fd5b5f3560e01c8063760f2a0b14610054578063a8afbddd1461004f578063b066aafc1461004a5763f887ea4014610045575f80fd5b610152565b610130565b6100b7565b346100b35760203660031901126100b35760043567ffffffffffffffff81116100b357366023820112156100b357806004013567ffffffffffffffff81116100b3573660248260051b840101116100b35760246100b19201610196565b005b5f80fd5b346100b35760203660031901126100b35760043563ffffffff81168091036100b3576100e1610370565b8015610121576020817f75de08d29c16281a600d57180e47c4ad749f743264f4bf5670477b14871361989263ffffffff195f5416175f55604051908152a1005b6305691de360e41b5f5260045ffd5b346100b3575f3660031901126100b357602063ffffffff5f5416604051908152f35b346100b3575f3660031901126100b3576040517f00000000000000000000000016323707e61d20a39aae5ab64808e480b91658ab6001600160a01b03168152602090f35b61019e610370565b5f915b8083106101ad57505050565b6101bb838284959694610282565b936101f96101d66101cf60208801886102b8565b36916102ff565b6101df8761035c565b6040880135906101f35f5463ffffffff1690565b926103b1565b50806102295761022661020b8761035c565b630b8b4c9960e11b5f526001600160a01b0316600452602490565b5ffd5b7fa5ce1292f71836359bea068ac79cd2424ca8f795c7421606753cb7bcbee5f3db610276600194959697610262868060a01b039161035c565b604051941515855216929081906020820190565b0390a2019192906101a1565b91908110156102a45760051b81013590605e19813603018212156100b3570190565b634e487b7160e01b5f52603260045260245ffd5b903590601e19813603018212156100b3570180359067ffffffffffffffff82116100b3576020019181360383136100b357565b634e487b7160e01b5f52604160045260245ffd5b92919267ffffffffffffffff82116103575760405191601f8101601f19908116603f0116830167ffffffffffffffff811184821017610357576040528294818452818301116100b3578281602093845f960137010152565b6102eb565b356001600160a01b03811681036100b35790565b7f00000000000000000000000016323707e61d20a39aae5ab64808e480b91658ab6001600160a01b031633036103a257565b639165520160e01b5f5260045ffd5b5f949385939192905a90808210156103cd575b50505050509190565b8391038060061c9003116103e3575b80806103c4565b5f949550849350839060208451940192f1906001915f80806103dc56fea26469706673582212204d12376dceb1947c84ce8f839c52cf765b533839d2b6a0764451befbc5f8686764736f6c634300081e0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000016323707e61d20a39aae5ab64808e480b91658ab

-----Decoded View---------------
Arg [0] : router_ (address): 0x16323707e61d20A39AaE5ab64808e480B91658aB

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 00000000000000000000000016323707e61d20a39aae5ab64808e480b91658ab


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.