contract CallsScript is BaseEVMScriptExecutor {
    using ScriptHelpers for bytes;

    /* Hardcoded constants to save gas
    bytes32 internal constant EXECUTOR_TYPE = keccak256("CALLS_SCRIPT");
    */
    bytes32 internal constant EXECUTOR_TYPE = 0x2dc858a00f3e417be1394b87c07158e989ec681ce8cc68a9093680ac1a870302;

    string private constant ERROR_BLACKLISTED_CALL = "EVMCALLS_BLACKLISTED_CALL";
    string private constant ERROR_INVALID_LENGTH = "EVMCALLS_INVALID_LENGTH";

    /* This is manually crafted in assembly
    string private constant ERROR_CALL_REVERTED = "EVMCALLS_CALL_REVERTED";
    */

    event LogScriptCall(address indexed sender, address indexed src, address indexed dst);

    /**
    * @notice Executes a number of call scripts
    * @param _script [ specId (uint32) ] many calls with this structure ->
    *    [ to (address: 20 bytes) ] [ calldataLength (uint32: 4 bytes) ] [ calldata (calldataLength bytes) ]
    * @param _blacklist Addresses the script cannot call to, or will revert.
    * @return Always returns empty byte array
    */
    function execScript(bytes _script, bytes, address[] _blacklist) external isInitialized returns (bytes) {
        uint256 location = SCRIPT_START_LOCATION; // first 32 bits are spec id
        while (location < _script.length) {
            // Check there's at least address + calldataLength available
            require(_script.length - location >= 0x18, ERROR_INVALID_LENGTH);

            address contractAddress = _script.addressAt(location);
            // Check address being called is not blacklist
            for (uint256 i = 0; i < _blacklist.length; i++) {
                require(contractAddress != _blacklist[i], ERROR_BLACKLISTED_CALL);
            }

            // logged before execution to ensure event ordering in receipt
            // if failed entire execution is reverted regardless
            emit LogScriptCall(msg.sender, address(this), contractAddress);

            uint256 calldataLength = uint256(_script.uint32At(location + 0x14));
            uint256 startOffset = location + 0x14 + 0x04;
            uint256 calldataStart = _script.locationOf(startOffset);

            // compute end of script / next location
            location = startOffset + calldataLength;
            require(location <= _script.length, ERROR_INVALID_LENGTH);

            bool success;
            assembly {
                success := call(
                    sub(gas, 5000),       // forward gas left - 5000
                    contractAddress,      // address
                    0,                    // no value
                    calldataStart,        // calldata start
                    calldataLength,       // calldata length
                    0,                    // don't write output
                    0                     // don't write output
                )

                switch success
                case 0 {
                    let ptr := mload(0x40)

                    switch returndatasize
                    case 0 {
                        // No error data was returned, revert with "EVMCALLS_CALL_REVERTED"
                        // See remix: doing a `revert("EVMCALLS_CALL_REVERTED")` always results in
                        // this memory layout
                        mstore(ptr, 0x08c379a000000000000000000000000000000000000000000000000000000000)         // error identifier
                        mstore(add(ptr, 0x04), 0x0000000000000000000000000000000000000000000000000000000000000020) // starting offset
                        mstore(add(ptr, 0x24), 0x0000000000000000000000000000000000000000000000000000000000000016) // reason length
                        mstore(add(ptr, 0x44), 0x45564d43414c4c535f43414c4c5f524556455254454400000000000000000000) // reason

                        revert(ptr, 100) // 100 = 4 + 3 * 32 (error identifier + 3 words for the ABI encoded error)
                    }
                    default {
                        // Forward the full error data
                        returndatacopy(ptr, 0, returndatasize)
                        revert(ptr, returndatasize)
                    }
                }
                default { }
            }
        }
        // No need to allocate empty bytes for the return as this can only be called via an delegatecall
        // (due to the isInitialized modifier)
    }

    function executorType() external pure returns (bytes32) {
        return EXECUTOR_TYPE;
    }
}
// Inspired by https://github.com/reverendus/tx-manager
function execScript(bytes _script, bytes, address[] _blacklist) external isInitialized returns (bytes) {
    uint256 location = SCRIPT_START_LOCATION; // first 32 bits are spec id
    while (location < _script.length) {
        // Check there's at least address + calldataLength available
        require(_script.length - location >= 0x18, ERROR_INVALID_LENGTH);

        address contractAddress = _script.addressAt(location);
        // Check address being called is not blacklist
        for (uint256 i = 0; i < _blacklist.length; i++) {
            require(contractAddress != _blacklist[i], ERROR_BLACKLISTED_CALL);
        }

        // logged before execution to ensure event ordering in receipt
        // if failed entire execution is reverted regardless
        emit LogScriptCall(msg.sender, address(this), contractAddress);

        uint256 calldataLength = uint256(_script.uint32At(location + 0x14));
        uint256 startOffset = location + 0x14 + 0x04;
        uint256 calldataStart = _script.locationOf(startOffset);

        // compute end of script / next location
        location = startOffset + calldataLength;
        require(location <= _script.length, ERROR_INVALID_LENGTH);

        bool success;
        assembly {
            success := call(
                sub(gas, 5000),       // forward gas left - 5000
                contractAddress,      // address
                0,                    // no value
                calldataStart,        // calldata start
                calldataLength,       // calldata length
                0,                    // don't write output
                0                     // don't write output
            )

            switch success
            case 0 {
                let ptr := mload(0x40)

                switch returndatasize
                case 0 {
                    // No error data was returned, revert with "EVMCALLS_CALL_REVERTED"
                    // See remix: doing a `revert("EVMCALLS_CALL_REVERTED")` always results in
                    // this memory layout
                    mstore(ptr, 0x08c379a000000000000000000000000000000000000000000000000000000000)         // error identifier
                    mstore(add(ptr, 0x04), 0x0000000000000000000000000000000000000000000000000000000000000020) // starting offset
                    mstore(add(ptr, 0x24), 0x0000000000000000000000000000000000000000000000000000000000000016) // reason length
                    mstore(add(ptr, 0x44), 0x45564d43414c4c535f43414c4c5f524556455254454400000000000000000000) // reason

                    revert(ptr, 100) // 100 = 4 + 3 * 32 (error identifier + 3 words for the ABI encoded error)
                }
                default {
                    // Forward the full error data
                    returndatacopy(ptr, 0, returndatasize)
                    revert(ptr, returndatasize)
                }
            }
            default { }
        }
    }
    // No need to allocate empty bytes for the return as this can only be called via an delegatecall
    // (due to the isInitialized modifier)
}

/**
 * @notice Executes a number of call scripts
 * @param _script [ specId (uint32) ] many calls with this structure ->
 *    [ to (address: 20 bytes) ] [ calldataLength (uint32: 4 bytes) ] [ calldata (calldataLength bytes) ]
 * @param _blacklist Addresses the script cannot call to, or will revert.
 * @return Always returns empty byte array
 */