ETH Price: $1,965.49 (-0.51%)

Transaction Decoder

Block:
23831696 at Nov-19-2025 07:39:35 AM +UTC
Transaction Fee:
0.000027141739146885 ETH $0.05
Gas Used:
92,035 Gas / 0.294906711 Gwei

Emitted Events:

Account State Difference:

  Address   Before After State Difference Code
0x187c0E0a...dC3Ea8e0f
(CoinList 4)
(Lido: Execution Layer Rewards Vault)
33.406969598227837483 Eth33.406979018010087483 Eth0.00000941978225
0xcccCCCcC...1FF4e8A94
0xD2C82F2e...664610998
(CoinList 3)
18.846930420513900505 Eth
Nonce: 2763010
18.84690327877475362 Eth
Nonce: 2763011
0.000027141739146885

Execution Trace

CoinList 4.0dcd7a6c( )
  • Null: 0x000...001.a85f230b( )
  • CFG.transfer( to=0xD1669Ac6044269b59Fa12c5822439F609Ca54F41, value=2000000000000000000000000 ) => ( success=True )
    // SPDX-License-Identifier: GPL-2.0-or-later
    pragma solidity 0.8.28;
    import {DelegationToken} from "src/DelegationToken.sol";
    import {ICFG} from "src/interfaces/ICFG.sol";
    /// @title  Centrifuge Token
    contract CFG is DelegationToken, ICFG {
        constructor(address ward) DelegationToken(18) {
            file("name", "Centrifuge");
            file("symbol", "CFG");
            rely(ward);
        }
        /// @notice Burns sender's tokens.
        function burn(uint256 value) external {
            uint256 balance = balanceOf(msg.sender);
            require(balance >= value, InsufficientBalance());
            unchecked {
                // We don't need overflow checks b/c require(balance >= value) and balance <= totalSupply
                _setBalance(msg.sender, balance - value);
                totalSupply -= value;
            }
            _moveDelegateVotes(delegatee[msg.sender], address(0), value);
            emit Transfer(msg.sender, address(0), value);
        }
    }
    // SPDX-License-Identifier: GPL-2.0-or-later
    pragma solidity 0.8.28;
    import {ERC20} from "protocol-v3/misc/ERC20.sol";
    import {IERC20} from "protocol-v3/misc/interfaces/IERC20.sol";
    import {IDelegationToken, Delegation, Signature} from "src/interfaces/IDelegationToken.sol";
    /// @title  Delegation Token
    /// @notice Extension of ERC20 to support token delegation
    ///         This extension keeps track of the current voting power delegated to each account. Voting power can be
    ///         delegated either by calling the `delegate` function directly, or by providing a signature to be
    ///         used with `delegateBySig`.
    ///
    ///         This enables onchain votes on external voting smart contracts leveraging storage proofs.
    ///
    ///         By default, token balance does not account for voting power. This makes transfers cheaper. Whether
    ///         an account has to self-delegate to vote depends on the voting contract implementation.
    /// @author Modified from https://github.com/morpho-org/morpho-token
    contract DelegationToken is ERC20, IDelegationToken {
        bytes32 public constant DELEGATION_TYPEHASH =
            keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
        /// @inheritdoc IDelegationToken
        mapping(address => address) public delegatee;
        /// @inheritdoc IDelegationToken
        mapping(address => uint256) public delegatedVotingPower;
        /// @inheritdoc IDelegationToken
        mapping(address => uint256) public delegationNonce;
        constructor(uint8 decimals_) ERC20(decimals_) {}
        /// @inheritdoc IDelegationToken
        function delegate(address newDelegatee) external {
            address delegator = msg.sender;
            _delegate(delegator, newDelegatee);
        }
        /// @inheritdoc IDelegationToken
        function delegateWithSig(Delegation calldata delegation, Signature calldata signature) external {
            require(block.timestamp <= delegation.expiry, DelegatesExpiredSignature());
            bytes32 digest = keccak256(
                abi.encodePacked("\\x19\\x01", DOMAIN_SEPARATOR(), keccak256(abi.encode(DELEGATION_TYPEHASH, delegation)))
            );
            address delegator = ecrecover(digest, signature.v, signature.r, signature.s);
            require(delegator != address(0), InvalidSignature());
            require(delegation.nonce == delegationNonce[delegator]++, InvalidDelegationNonce());
            _delegate(delegator, delegation.delegatee);
        }
        /// @dev Delegates the balance of the `delegator` to `newDelegatee`.
        function _delegate(address delegator, address newDelegatee) internal {
            address oldDelegatee = delegatee[delegator];
            delegatee[delegator] = newDelegatee;
            emit DelegateeChanged(delegator, oldDelegatee, newDelegatee);
            _moveDelegateVotes(oldDelegatee, newDelegatee, balanceOf(delegator));
        }
        /// @dev Moves voting power when tokens are transferred.
        function transfer(address to, uint256 value) public override(ERC20, IERC20) returns (bool success) {
            success = super.transfer(to, value);
            _moveDelegateVotes(delegatee[msg.sender], delegatee[to], value);
        }
        /// @dev Moves voting power when tokens are transferred.
        function transferFrom(address from, address to, uint256 value)
            public
            override(ERC20, IERC20)
            returns (bool success)
        {
            success = super.transferFrom(from, to, value);
            _moveDelegateVotes(delegatee[from], delegatee[to], value);
        }
        /// @dev Adds voting power when tokens are minted.
        function mint(address to, uint256 value) public override(ERC20) {
            super.mint(to, value);
            _moveDelegateVotes(address(0), delegatee[to], value);
        }
        /// @dev Removes voting power when tokens are burned.
        function burn(address from, uint256 value) public override(ERC20) {
            super.burn(from, value);
            _moveDelegateVotes(delegatee[from], address(0), value);
        }
        /// @dev Moves delegated votes from one delegate to another.
        function _moveDelegateVotes(address from, address to, uint256 amount) internal {
            if (from != to && amount > 0) {
                if (from != address(0)) {
                    uint256 oldValue = delegatedVotingPower[from];
                    uint256 newValue = oldValue - amount;
                    delegatedVotingPower[from] = newValue;
                    emit DelegatedVotingPowerChanged(from, oldValue, newValue);
                }
                if (to != address(0)) {
                    uint256 oldValue = delegatedVotingPower[to];
                    uint256 newValue = oldValue + amount;
                    delegatedVotingPower[to] = newValue;
                    emit DelegatedVotingPowerChanged(to, oldValue, newValue);
                }
            }
        }
    }
    // SPDX-License-Identifier: GPL-2.0-or-later
    pragma solidity 0.8.28;
    import {IDelegationToken} from "src/interfaces/IDelegationToken.sol";
    interface ICFG is IDelegationToken {
        /// @notice Burns sender's tokens.
        function burn(uint256 value) external;
    }
    // SPDX-License-Identifier: BUSL-1.1
    pragma solidity 0.8.28;
    import {Auth} from "src/misc/Auth.sol";
    import {EIP712Lib} from "src/misc/libraries/EIP712Lib.sol";
    import {SignatureLib} from "src/misc/libraries/SignatureLib.sol";
    import {IERC20, IERC20Metadata, IERC20Permit} from "src/misc/interfaces/IERC20.sol";
    /// @title  ERC20
    /// @notice Standard ERC-20 implementation, with mint/burn functionality and permit logic.
    /// @author Modified from https://github.com/makerdao/xdomain-dss/blob/master/src/Dai.sol
    contract ERC20 is Auth, IERC20Metadata, IERC20Permit {
        error FileUnrecognizedWhat();
        /// @inheritdoc IERC20Metadata
        string public name;
        /// @inheritdoc IERC20Metadata
        string public symbol;
        /// @inheritdoc IERC20Metadata
        uint8 public immutable decimals;
        /// @inheritdoc IERC20
        uint256 public totalSupply;
        mapping(address => uint256) private balances;
        /// @inheritdoc IERC20
        mapping(address => mapping(address => uint256)) public allowance;
        /// @inheritdoc IERC20Permit
        mapping(address => uint256) public nonces;
        // --- EIP712 ---
        bytes32 private immutable nameHash;
        bytes32 private immutable versionHash;
        uint256 public immutable deploymentChainId;
        bytes32 private immutable _DOMAIN_SEPARATOR;
        bytes32 public constant PERMIT_TYPEHASH =
            keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
        // --- Events ---
        event File(bytes32 indexed what, string data);
        constructor(uint8 decimals_) Auth(msg.sender) {
            decimals = decimals_;
            nameHash = keccak256(bytes("Centrifuge"));
            versionHash = keccak256(bytes("1"));
            deploymentChainId = block.chainid;
            _DOMAIN_SEPARATOR = EIP712Lib.calculateDomainSeparator(nameHash, versionHash);
        }
        function _balanceOf(address user) internal view virtual returns (uint256) {
            return balances[user];
        }
        /// @inheritdoc IERC20
        function balanceOf(address user) public view virtual returns (uint256) {
            return _balanceOf(user);
        }
        function _setBalance(address user, uint256 value) internal virtual {
            balances[user] = value;
        }
        /// @inheritdoc IERC20Permit
        function DOMAIN_SEPARATOR() public view returns (bytes32) {
            return block.chainid == deploymentChainId
                ? _DOMAIN_SEPARATOR
                : EIP712Lib.calculateDomainSeparator(nameHash, versionHash);
        }
        // --- Administration ---
        function file(bytes32 what, string memory data) public virtual auth {
            if (what == "name") name = data;
            else if (what == "symbol") symbol = data;
            else revert FileUnrecognizedWhat();
            emit File(what, data);
        }
        // --- ERC20 Mutations ---
        /// @inheritdoc IERC20
        function transfer(address to, uint256 value) public virtual returns (bool) {
            require(to != address(0) && to != address(this), InvalidAddress());
            uint256 balance = balanceOf(msg.sender);
            require(balance >= value, InsufficientBalance());
            unchecked {
                _setBalance(msg.sender, _balanceOf(msg.sender) - value);
                // note: we don't need an overflow check here b/c sum of all balances == totalSupply
                _setBalance(to, _balanceOf(to) + value);
            }
            emit Transfer(msg.sender, to, value);
            return true;
        }
        /// @inheritdoc IERC20
        function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
            return _transferFrom(msg.sender, from, to, value);
        }
        function _transferFrom(address sender, address from, address to, uint256 value) internal virtual returns (bool) {
            require(to != address(0) && to != address(this), InvalidAddress());
            uint256 balance = balanceOf(from);
            require(balance >= value, InsufficientBalance());
            if (from != sender) {
                uint256 allowed = allowance[from][sender];
                if (allowed != type(uint256).max) {
                    require(allowed >= value, InsufficientAllowance());
                    unchecked {
                        allowance[from][sender] = allowed - value;
                    }
                }
            }
            unchecked {
                _setBalance(from, _balanceOf(from) - value);
                // note: we don't need an overflow check here b/c sum of all balances == totalSupply
                _setBalance(to, _balanceOf(to) + value);
            }
            emit Transfer(from, to, value);
            return true;
        }
        /// @inheritdoc IERC20
        function approve(address spender, uint256 value) external returns (bool) {
            allowance[msg.sender][spender] = value;
            emit Approval(msg.sender, spender, value);
            return true;
        }
        // --- Mint/Burn ---
        function mint(address to, uint256 value) public virtual auth {
            require(to != address(0) && to != address(this), InvalidAddress());
            unchecked {
                // We don't need an overflow check here b/c balances[to] <= totalSupply
                // and there is an overflow check below
                _setBalance(to, _balanceOf(to) + value);
            }
            totalSupply = totalSupply + value;
            emit Transfer(address(0), to, value);
        }
        function burn(address from, uint256 value) public virtual auth {
            uint256 balance = balanceOf(from);
            require(balance >= value, InsufficientBalance());
            if (from != msg.sender) {
                uint256 allowed = allowance[from][msg.sender];
                if (allowed != type(uint256).max) {
                    require(allowed >= value, InsufficientAllowance());
                    unchecked {
                        allowance[from][msg.sender] = allowed - value;
                    }
                }
            }
            unchecked {
                // We don't need overflow checks b/c require(balance >= value) and balance <= totalSupply
                _setBalance(from, _balanceOf(from) - value);
                totalSupply = totalSupply - value;
            }
            emit Transfer(from, address(0), value);
        }
        // --- Approve by signature ---
        function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) public {
            require(block.timestamp <= deadline, PermitExpired());
            uint256 nonce;
            unchecked {
                nonce = nonces[owner]++;
            }
            bytes32 digest = keccak256(
                abi.encodePacked(
                    "\\x19\\x01",
                    DOMAIN_SEPARATOR(),
                    keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline))
                )
            );
            require(SignatureLib.isValidSignature(owner, digest, signature), InvalidPermit());
            allowance[owner][spender] = value;
            emit Approval(owner, spender, value);
        }
        /// @inheritdoc IERC20Permit
        function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
            external
        {
            permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
        }
    }
    // SPDX-License-Identifier: BUSL-1.1
    pragma solidity 0.8.28;
    /// @title  IERC20
    /// @dev    Interface of the ERC20 standard as defined in the EIP.
    /// @author Modified from OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
    interface IERC20 {
        error InvalidAddress();
        error InsufficientBalance();
        error InsufficientAllowance();
        /**
         * @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 value of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the value of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
        /**
         * @dev Moves a `value` amount of tokens from `from` to `to` using the
         * allowance mechanism. `value` 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 value) external returns (bool);
    }
    /**
     * @dev Interface for the optional metadata functions from the ERC20 standard.
     */
    interface IERC20Metadata is IERC20 {
        /**
         * @dev Returns the name of the token.
         */
        function name() external view returns (string memory);
        /**
         * @dev Returns the symbol of the token.
         */
        function symbol() external view returns (string memory);
        /**
         * @dev Returns the decimals places of the token.
         */
        function decimals() external view returns (uint8);
    }
    /**
     * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     *
     * ==== Security Considerations
     *
     * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
     * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
     * considered as an intention to spend the allowance in any specific way. The second is that because permits have
     * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
     * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
     * generally recommended is:
     *
     * ```solidity
     * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
     *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
     *     doThing(..., value);
     * }
     *
     * function doThing(..., uint256 value) public {
     *     token.safeTransferFrom(msg.sender, address(this), value);
     *     ...
     * }
     * ```
     *
     * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
     * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
     * {SafeERC20-safeTransferFrom}).
     *
     * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
     * contracts should have entry points that don't rely on permit.
     */
    interface IERC20Permit {
        error PermitExpired();
        error InvalidPermit();
        /**
         * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
         * given ``owner``'s signed approval.
         *
         * IMPORTANT: The same issues {IERC20-approve} has related to transaction
         * ordering also apply here.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `deadline` must be a timestamp in the future.
         * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
         * over the EIP712-formatted function arguments.
         * - the signature must use ``owner``'s current nonce (see {nonces}).
         *
         * For more information on the signature format, see the
         * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
         * section].
         *
         * CAUTION: See Security Considerations above.
         */
        function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
            external;
        /**
         * @dev Returns the current nonce for `owner`. This value must be
         * included whenever a signature is generated for {permit}.
         *
         * Every successful call to {permit} increases ``owner``'s nonce by one. This
         * prevents a signature from being used multiple times.
         */
        function nonces(address owner) external view returns (uint256);
        /**
         * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32);
    }
    interface IERC20Wrapper {
        /**
         * @dev Returns the address of the underlying ERC-20 token that is being wrapped.
         */
        function underlying() external view returns (address);
        /**
         * @dev Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens.
         */
        function depositFor(address account, uint256 value) external returns (bool);
        /**
         * @dev Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens.
         */
        function withdrawTo(address account, uint256 value) external returns (bool);
    }
    // SPDX-License-Identifier: GPL-2.0-or-later
    pragma solidity 0.8.28;
    import {IERC20Metadata, IERC20Permit} from "protocol-v3/misc/interfaces/IERC20.sol";
    struct Delegation {
        address delegatee;
        uint256 nonce;
        uint256 expiry;
    }
    struct Signature {
        uint8 v;
        bytes32 r;
        bytes32 s;
    }
    interface IDelegationToken is IERC20Metadata, IERC20Permit {
        /// @notice Emitted when an delegator changes their delegatee.
        event DelegateeChanged(address indexed delegator, address indexed oldDelegatee, address indexed newDelegatee);
        /// @notice Emitted when a delegatee's delegated voting power changes.
        event DelegatedVotingPowerChanged(address indexed delegatee, uint256 oldVotes, uint256 newVotes);
        /// @notice The signature used has expired.
        error DelegatesExpiredSignature();
        /// @notice The delegation nonce used by the signer is not its current delegation nonce.
        error InvalidDelegationNonce();
        /// @notice The signature was invalid.
        error InvalidSignature();
        /// @notice Returns the delegatee that `account` has chosen.
        function delegatee(address account) external view returns (address);
        /// @notice Returns the current voting power delegated to `account`.
        function delegatedVotingPower(address account) external view returns (uint256);
        /// @notice Returns the current delegation nonce of `account`.
        function delegationNonce(address account) external view returns (uint256);
        /// @notice Delegates the balance of the sender to `newDelegatee`.
        /// @dev Delegating to the zero address effectively removes the delegation, incidentally making transfers cheaper.
        /// @dev Delegating to the previous delegatee does not revert.
        function delegate(address newDelegatee) external;
        /// @notice Delegates the balance of the signer to `newDelegatee`.
        /// @dev Delegating to the zero address effectively removes the delegation, incidentally making transfers cheaper.
        /// @dev Delegating to the previous delegatee effectively revokes past signatures with the same nonce.
        function delegateWithSig(Delegation calldata delegation, Signature calldata signature) external;
    }
    // SPDX-License-Identifier: BUSL-1.1
    pragma solidity 0.8.28;
    import {IAuth} from "src/misc/interfaces/IAuth.sol";
    /// @title  Auth
    /// @notice Simple authentication pattern
    /// @author Based on code from https://github.com/makerdao/dss
    abstract contract Auth is IAuth {
        /// @inheritdoc IAuth
        mapping(address => uint256) public wards;
        constructor(address initialWard) {
            wards[initialWard] = 1;
            emit Rely(initialWard);
        }
        /// @dev Check if the msg.sender has permissions
        modifier auth() {
            require(wards[msg.sender] == 1, NotAuthorized());
            _;
        }
        /// @inheritdoc IAuth
        function rely(address user) public auth {
            wards[user] = 1;
            emit Rely(user);
        }
        /// @inheritdoc IAuth
        function deny(address user) public auth {
            wards[user] = 0;
            emit Deny(user);
        }
    }
    // SPDX-License-Identifier: BUSL-1.1
    pragma solidity 0.8.28;
    /// @title  EIP712 Lib
    library EIP712Lib {
        // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
        bytes32 public constant EIP712_DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
        function calculateDomainSeparator(bytes32 nameHash, bytes32 versionHash) internal view returns (bytes32) {
            return keccak256(abi.encode(EIP712_DOMAIN_TYPEHASH, nameHash, versionHash, block.chainid, address(this)));
        }
    }
    // SPDX-License-Identifier: BUSL-1.1
    pragma solidity 0.8.28;
    interface IERC1271 {
        function isValidSignature(bytes32, bytes memory) external view returns (bytes4);
    }
    /// @title  Signature Lib
    library SignatureLib {
        error InvalidSigner();
        function isValidSignature(address signer, bytes32 digest, bytes memory signature)
            internal
            view
            returns (bool valid)
        {
            require(signer != address(0), InvalidSigner());
            if (signature.length == 65) {
                bytes32 r;
                bytes32 s;
                uint8 v;
                assembly {
                    r := mload(add(signature, 0x20))
                    s := mload(add(signature, 0x40))
                    v := byte(0, mload(add(signature, 0x60)))
                }
                if (signer == ecrecover(digest, v, r, s)) {
                    return true;
                }
            }
            if (signer.code.length > 0) {
                (bool success, bytes memory result) =
                    signer.staticcall(abi.encodeCall(IERC1271.isValidSignature, (digest, signature)));
                valid =
                    (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
            }
        }
    }
    // SPDX-License-Identifier: BUSL-1.1
    pragma solidity 0.8.28;
    interface IAuth {
        event Rely(address indexed user);
        event Deny(address indexed user);
        error NotAuthorized();
        /// @notice Returns whether the target is a ward (has admin access)
        function wards(address target) external view returns (uint256);
        /// @notice Make user a ward (give them admin access)
        function rely(address user) external;
        /// @notice Remove user as a ward (remove admin access)
        function deny(address user) external;
    }