// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { IMandateExecutionAdapterV3 } from "./IMandateExecutionAdapterV3.sol";

interface IMandateRegistryExecutionV3 {
    function consumeExecution(
        bytes calldata encodedMandate,
        bytes32 action,
        address target,
        bytes4 selector,
        uint256 amount,
        address operator,
        bytes32 resource,
        bytes32 domain,
        bytes32 callDataHash,
        uint256 value
    ) external returns (bytes32 mandateHash);
}

/// @notice Canonical native-ETH mandate executor.
/// @dev The registry allowlists this exact runtime code and verifies its
///      registry/domain bindings. This adapter binds every downstream call
///      field before consumption; failed calls revert the consumption update.
contract MandateExecutionAdapterV3 is IMandateExecutionAdapterV3 {
    bytes32 public constant EXECUTION_DOMAIN = keccak256("VAULTFIRE_V3_MANDATE_NATIVE_EXECUTION");

    address public override mandateRegistry;
    uint256 private _entered;

    error ZeroAddress();
    error TargetNotContract();
    error BadCallData();
    error ValueMismatch();
    error ReentrantCall();
    error DownstreamCallFailed();

    struct Execution {
        bytes32 action;
        bytes32 resource;
        address target;
        uint256 amount;
        bytes data;
    }

    constructor(address _mandateRegistry) {
        if (_mandateRegistry == address(0) || _mandateRegistry.code.length == 0) revert ZeroAddress();
        mandateRegistry = _mandateRegistry;
    }

    modifier nonReentrant() {
        if (_entered != 0) revert ReentrantCall();
        _entered = 1;
        _;
        _entered = 0;
    }

    function executionDomain() external pure override returns (bytes32) {
        return EXECUTION_DOMAIN;
    }

    function execute(bytes calldata encodedMandate, Execution calldata execution)
        external
        payable
        nonReentrant
        returns (bytes32 mandateHash, bytes memory result)
    {
        if (execution.target.code.length == 0) revert TargetNotContract();
        if (execution.data.length < 4) revert BadCallData();
        if (msg.value != execution.amount) revert ValueMismatch();

        bytes calldata data = execution.data;
        bytes4 selector;
        assembly {
            selector := calldataload(data.offset)
        }
        mandateHash = IMandateRegistryExecutionV3(mandateRegistry)
            .consumeExecution(
                encodedMandate,
                execution.action,
                execution.target,
                selector,
                execution.amount,
                msg.sender,
                execution.resource,
                EXECUTION_DOMAIN,
                keccak256(execution.data),
                msg.value
            );

        (bool ok, bytes memory returndata) = execution.target.call{ value: msg.value }(execution.data);
        if (!ok) revert DownstreamCallFailed();
        return (mandateHash, returndata);
    }
}
