SimpleSPVContract

Description:

Smart contract deployed on Ethereum with Factory features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/src/demo/SimpleSPVContract.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "../libs/BlockHeader.sol";
import "../libs/TxMerkleProof.sol";
import "../libs/TargetsHelper.sol";
import "../core/BitcoinTxParser.sol";

/**
 * @title SimpleSPVContract
 * @notice Simplified SPV for demo: 6 confirmations instead of 100
 * @dev Minimal Bitcoin SPV verification without difficulty adjustment
 */
contract SimpleSPVContract {
    // DEMO: 6 confirmations instead of 100
    uint32 public constant COINBASE_MATURITY = 6;

    struct BlockData {
        bytes32 prev;
        bytes32 merkleRoot;
        uint32 version;
        uint32 time;
        uint32 nonce;
        uint32 bits;
        uint64 height;
        uint256 cumulativeWork;  // Total work up to this block
    }

    mapping(bytes32 => BlockData) public blocks;
    mapping(uint64 => bytes32) public height2hash;
    bytes32 public mainchainHead;

    // Trusted block configuration
    bytes32 public immutable trustedBlockHash;
    uint64 public immutable trustedBlockHeight;

    // Pool UTXO tracking for source verification
    mapping(bytes32 => mapping(uint32 => bool)) public poolUTXOs; // txid => vout => isPoolUTXO
    address public poolContract;

    event BlockHeaderAdded(uint64 height, bytes32 blockHash);
    event PoolUTXORegistered(bytes32 indexed txid, uint32 vout);
    event PoolUTXOSpent(bytes32 indexed txid, uint32 vout);
    event ReorgOccurred(bytes32 oldHead, bytes32 newHead, uint64 depth);
    event MainchainHeadUpdated(uint64 height, bytes32 head);

    error InvalidHeaderLength();
    error BlockAlreadyExists(bytes32);
    error PrevNotFound(bytes32);
    error OnlyPoolContract();
    error InvalidTxSource();
    error ReorgTooDeep();

    /**
     * @notice Constructor with optional trusted block
     * @param _trustedBlockHash Hash of the trusted block (or bytes32(0) for genesis)
     * @param _trustedBlockHeight Height of the trusted block
     * @param _trustedMerkleRoot Merkle root of the trusted block
     */
    constructor(
        bytes32 _trustedBlockHash,
        uint64 _trustedBlockHeight,
        bytes32 _trustedMerkleRoot
    ) {
        trustedBlockHash = _trustedBlockHash;
        trustedBlockHeight = _trustedBlockHeight;

        // If a trusted block is specified, add it to the chain
        if (_trustedBlockHash != bytes32(0)) {
            // Calculate work for trusted block
            uint256 target = TargetsHelper.bitsToTarget(0x1d00ffff);
            uint256 work = TargetsHelper.workFromTarget(target);

            blocks[_trustedBlockHash] = BlockData({
                prev: bytes32(0),
                merkleRoot: _trustedMerkleRoot,
                version: 1,
                time: uint32(block.timestamp),
                nonce: 0,
                bits: 0x1d00ffff,
                height: _trustedBlockHeight,
                cumulativeWork: work * _trustedBlockHeight  // Approximate cumulative work
            });
            height2hash[_trustedBlockHeight] = _trustedBlockHash;
            mainchainHead = _trustedBlockHash;
            emit BlockHeaderAdded(_trustedBlockHeight, _trustedBlockHash);
        }
    }

    /**
     * @notice Set the pool contract address (can only be set once)
     * @param _poolContract Address of the mining pool contract
     */
    function setPoolContract(address _poolContract) external {
        require(poolContract == address(0), "Pool already set");
        require(_poolContract != address(0), "Invalid pool address");
        poolContract = _poolContract;
    }

    modifier onlyPool() {
        if (msg.sender != poolContract) revert OnlyPoolContract();
        _;
    }

    // ======== Views ========

    function blockExists(bytes32 bh) public view returns (bool) {
        return blocks[bh].time != 0;
    }

    function isInMainchain(bytes32 bh) public view returns (bool) {
        uint64 h = blocks[bh].height;
        return h != 0 && height2hash[h] == bh;
    }

    function getBlockStatus(bytes32 bh) external view returns (bool isIn, uint64 confirmations) {
        uint64 h = blocks[bh].height;
        if (h == 0) return (false, 0);
        isIn = (height2hash[h] == bh);
        if (!isIn) return (false, 0);

        uint64 headH = blocks[mainchainHead].height;
        confirmations = headH >= h ? headH - h + 1 : 0;
    }

    function isMature(bytes32 bh) external view returns (bool) {
        uint64 h = blocks[bh].height;
        if (h == 0 || !isInMainchain(bh)) return false;
        uint64 head = blocks[mainchainHead].height;
        return head >= h + COINBASE_MATURITY;
    }

    function getBlockInfo(bytes32 bh) external view returns (
        BlockData memory mainBlockData,
        bool isInMainchainResult,
        uint256 cumulativeWork,
        bool exists
    ) {
        mainBlockData = blocks[bh];
        exists = mainBlockData.time != 0;
        isInMainchainResult = isInMainchain(bh);
        cumulativeWork = mainBlockData.cumulativeWork;
    }

    function getMainchainHead() external view returns (bytes32) {
        return mainchainHead;
    }

    function getMainchainHeight() external view returns (uint64) {
        return blocks[mainchainHead].height;
    }

    function getBlockMerkleRoot(bytes32 bh) external view returns (bytes32) {
        return blocks[bh].merkleRoot;
    }

    function getBlockHeight(bytes32 bh) external view returns (uint64) {
        return blocks[bh].height;
    }

    function getBlockHash(uint64 height) external view returns (bytes32) {
        return height2hash[height];
    }

    function checkTxInclusion(
        bytes32 blockHash,
        bytes32 txid,
        bytes32[] calldata siblings,
        uint256 index
    ) external view returns (bool) {
        if (!blockExists(blockHash)) return false;

        bytes32 merkleRoot = blocks[blockHash].merkleRoot;

        // Verify merkle proof
        return TxMerkleProof.verify(siblings, index, merkleRoot, txid);
    }

    /**
     * @notice Register a pool UTXO for source verification
     * @param txid Transaction ID (little-endian)
     * @param vout Output index
     */
    function registerPoolUTXO(bytes32 txid, uint32 vout) external onlyPool {
        poolUTXOs[txid][vout] = true;
        emit PoolUTXORegistered(txid, vout);
    }

    /**
     * @notice Mark a pool UTXO as spent
     * @param txid Transaction ID (little-endian)
     * @param vout Output index
     */
    function markPoolUTXOSpent(bytes32 txid, uint32 vout) external onlyPool {
        poolUTXOs[txid][vout] = false;
        emit PoolUTXOSpent(txid, vout);
    }

    /**
     * @notice Verify that a transaction spends from pool UTXOs
     * @param rawTx Raw Bitcoin transaction bytes
     * @return isValid True if transaction spends from at least one pool UTXO
     */
    function verifyTxSource(bytes calldata rawTx) external view returns (bool isValid) {
        // Parse all inputs from the transaction
        (bytes32[] memory prevTxIds, uint32[] memory vouts) = BitcoinTxParser.parseAllInputs(rawTx);

        // Check if any input spends from a registered pool UTXO
        for (uint256 i = 0; i < prevTxIds.length; i++) {
            // Flip to little-endian for lookup
            bytes32 prevTxIdLE = BitcoinTxParser.flipBytes32(prevTxIds[i]);
            if (poolUTXOs[prevTxIdLE][vouts[i]]) {
                return true;
            }
        }

        return false;
    }

    /**
     * @notice Check if a specific UTXO is registered as a pool UTXO
     * @param txid Transaction ID (little-endian)
     * @param vout Output index
     */
    function isPoolUTXO(bytes32 txid, uint32 vout) external view returns (bool) {
        return poolUTXOs[txid][vout];
    }

    // ======== Mutations (Simplified) ========

    function addBlockHeader(bytes calldata raw) public {
        if (raw.length != 80) revert InvalidHeaderLength();

        (BlockHeaderData memory h, bytes32 bh) = BlockHeader.parseHeader(raw);
        if (blocks[bh].time != 0) revert BlockAlreadyExists(bh);

        if (h.prevBlockHash != bytes32(0) && blocks[h.prevBlockHash].time == 0) {
            revert PrevNotFound(h.prevBlockHash);
        }

        uint64 height = (h.prevBlockHash == bytes32(0)) ? 0 : blocks[h.prevBlockHash].height + 1;

        // Calculate work and cumulative work
        uint256 target = TargetsHelper.bitsToTarget(h.bits);
        uint256 work = TargetsHelper.workFromTarget(target);
        uint256 cumulative = (h.prevBlockHash == bytes32(0))
            ? work
            : addmod(blocks[h.prevBlockHash].cumulativeWork, work, type(uint256).max);

        blocks[bh] = BlockData({
            prev: h.prevBlockHash,
            merkleRoot: h.merkleRoot,
            version: h.version,
            time: h.time,
            nonce: h.nonce,
            bits: h.bits,
            height: height,
            cumulativeWork: cumulative
        });

        // Check if this is the new mainchain head
        if (mainchainHead == bytes32(0)) {
            mainchainHead = bh;
            height2hash[height] = bh;
            emit MainchainHeadUpdated(height, bh);
        } else {
            if (blocks[bh].cumulativeWork > blocks[mainchainHead].cumulativeWork) {
                // This fork has more work - trigger reorg
                _reorgTo(bh, height);
            } else if (h.prevBlockHash == mainchainHead) {
                // Simple extension of mainchain
                mainchainHead = bh;
                height2hash[height] = bh;
                emit MainchainHeadUpdated(height, bh);
            }
            // else: this is a fork with less work, ignore
        }

        emit BlockHeaderAdded(height, bh);
    }

    /**
     * @notice Reorganize the mainchain to a new head with more cumulative work
     * @param newHead The new mainchain head block hash
     * @param height The height of the new head
     */
    function _reorgTo(bytes32 newHead, uint64 height) internal {
        uint64 maxReorgDepth = 10;
        bytes32 cursor = newHead;
        uint64 h = height;
        bytes32 oldHead = mainchainHead;
        uint64 reorgCount = 0;

        unchecked {
            while (true) {
                // Stop if we've reached a block already on mainchain
                if (height2hash[h] == cursor) break;

                // Update height mapping
                height2hash[h] = cursor;

                // Move to parent
                bytes32 p = blocks[cursor].prev;
                if (p == bytes32(0)) break;

                cursor = p;
                --h;
                ++reorgCount;

                if (reorgCount > maxReorgDepth) revert ReorgTooDeep();
            }
        }

        mainchainHead = newHead;
        emit ReorgOccurred(oldHead, newHead, reorgCount);
        emit MainchainHeadUpdated(height, newHead);
    }

    // ======== Test Helpers ========

    function addBlockWithHash(bytes32 targetHash, uint64 targetHeight) external {
        // Calculate work for test block
        uint256 target = TargetsHelper.bitsToTarget(0x1d00ffff);
        uint256 work = TargetsHelper.workFromTarget(target);

        blocks[targetHash] = BlockData({
            prev: bytes32(0),
            merkleRoot: bytes32(0),
            version: 1,
            time: uint32(block.timestamp),
            nonce: 0,
            bits: 0x1d00ffff,
            height: targetHeight,
            cumulativeWork: work * (targetHeight + 1)  // Approximate cumulative work
        });
        height2hash[targetHeight] = targetHash;
        mainchainHead = targetHash;
        emit BlockHeaderAdded(targetHeight, targetHash);
    }

    function setBlockMerkleRoot(bytes32 blockHash, bytes32 merkleRoot) external {
        require(blockExists(blockHash), "Block not found");
        blocks[blockHash].merkleRoot = merkleRoot;
    }
}
"
    },
    "contracts/src/libs/BlockHeader.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "./BytesUtils.sol";
import "../core/BitcoinHash.sol";

/// @notice Структура, представляющая разобранный 80-байтный Bitcoin block header
struct BlockHeaderData {
    uint32 version;
    bytes32 prevBlockHash; // wire format (32 bytes)
    bytes32 merkleRoot;    // wire format (32 bytes)
    uint32 time;
    uint32 bits;
    uint32 nonce;
}

library BlockHeader {
    using BytesUtils for bytes;

    error InvalidHeaderLength();

    /// @notice Парсит 80-байтный raw header (wire format) и возвращает структуру и blockHash (sha256d)
    /// @param raw 80-byte block header (calldata)
    /// @return header разобранный HeaderData
    /// @return blockHash double-sha256(header) — тот самый block hash (как в wire)
    function parseHeader(bytes calldata raw) internal pure returns (BlockHeaderData memory header, bytes32 blockHash) {
        if (raw.length != 80) revert InvalidHeaderLength();

        header.version = raw.toUint32LE(0);

        // prevBlockHash (32 bytes) at offset 4
        bytes32 prev;
        bytes32 merkle;
        assembly {
            // calldataload loads 32 bytes starting at position (raw.offset + 4)
            prev := calldataload(add(raw.offset, 4))
            merkle := calldataload(add(raw.offset, 36))
        }
        header.prevBlockHash = prev;
        header.merkleRoot = merkle;

        header.time = raw.toUint32LE(68);
        header.bits = raw.toUint32LE(72);
        header.nonce = raw.toUint32LE(76);

        // blockHash = doubleSha256(raw)
        blockHash = BitcoinHash.doubleSha256(raw);
    }
}
"
    },
    "contracts/src/libs/TxMerkleProof.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "../core/BitcoinHash.sol";

/**
 * @notice Merkle proof verification for Bitcoin transaction id (txid).
 *
 * Условие: leaf и siblings должны быть в том же байтовом порядке (endianness),
 * что и merkleRoot, хранящийся в block header. В этом репозитории мы используем
 * wire-format double-sha256 output (sha256d) как стандартное представление.
 *
 * verify: берёт leaf, по списку siblings (от снизу к верх) и вычисляет root,
 * сравнивая с переданным root.
 */
library TxMerkleProof {
    /// @notice Проверяет inclusion proof.
    /// @param siblings массив sibling hashes (по уровню, снизу вверх)
    /// @param index позиция листа (начиная с 0) в уровне листьев (первоначальный индекс)
    /// @param root ожидаемый merkle root (как в block header)
    /// @param leaf txid (как bytes32)
    /// @return ok true если proof корректен
    function verify(bytes32[] calldata siblings, uint256 index, bytes32 root, bytes32 leaf) internal pure returns (bool ok) {
        bytes32 h = leaf;
        uint256 idx = index;

        for (uint256 i = 0; i < siblings.length; ++i) {
            bytes32 s = siblings[i];
            if ((idx & 1) == 0) {
                // current is left
                h = BitcoinHash.doubleSha256(abi.encodePacked(h, s));
            } else {
                // current is right
                h = BitcoinHash.doubleSha256(abi.encodePacked(s, h));
            }
            idx >>= 1;
        }
        return h == root;
    }
}
"
    },
    "contracts/src/libs/TargetsHelper.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/**
 * @title TargetsHelper
 * @notice Helper library for Bitcoin difficulty and work calculations
 */
library TargetsHelper {
    uint32 internal constant DIFFICULTY_ADJUSTMENT_INTERVAL = 2016;
    uint32 internal constant TARGET_TIME_PER_BLOCK = 600;
    uint32 internal constant TARGET_TIMESPAN = TARGET_TIME_PER_BLOCK * DIFFICULTY_ADJUSTMENT_INTERVAL;

    /**
     * @notice Convert compact difficulty bits to full 256-bit target
     * @param bits Compact difficulty representation (nBits)
     * @return 256-bit target value
     */
    function bitsToTarget(uint32 bits) internal pure returns (uint256) {
        uint256 exponent = uint8(bits >> 24);
        uint256 mantissa = uint256(bits & 0xFFFFFF);
        if (exponent == 0) return 0;
        if (exponent <= 3) {
            return mantissa >> (8 * (3 - exponent));
        } else {
            return mantissa * (1 << (8 * (exponent - 3)));
        }
    }

    /**
     * @notice Convert 256-bit target to compact difficulty bits
     * @param target Full 256-bit target
     * @return Compact difficulty representation (nBits)
     */
    function targetToBits(uint256 target) internal pure returns (uint32) {
        if (target == 0) return 0;
        uint256 n = _bytesLen(target);
        uint256 exponent = n;
        uint256 mantissa;

        if (n <= 3) {
            mantissa = target << (8 * (3 - n));
            exponent = 3;
        } else {
            mantissa = target >> (8 * (n - 3));
            if ((mantissa & 0x800000) != 0) {
                mantissa >>= 8;
                exponent = n + 1;
            }
        }

        return uint32((exponent << 24) | (mantissa & 0xFFFFFF));
    }

    /**
     * @notice Calculate work from target (higher target = lower work)
     * @param target The difficulty target
     * @return Work value (inversely proportional to target)
     */
    function workFromTarget(uint256 target) internal pure returns (uint256) {
        unchecked {
            return type(uint256).max / (target + 1);
        }
    }

    /**
     * @notice Check if a block height is a difficulty adjustment block
     * @param height Block height
     * @return True if this is an adjustment block
     */
    function isAdjustmentBlock(uint64 height) internal pure returns (bool) {
        return height != 0 && (height % DIFFICULTY_ADJUSTMENT_INTERVAL == 0);
    }

    /**
     * @notice Calculate new difficulty target based on actual timespan
     * @param prevTarget Previous difficulty target
     * @param actualTimespan Actual time taken for the interval
     * @return New difficulty target
     */
    function retarget(uint256 prevTarget, uint32 actualTimespan) internal pure returns (uint256) {
        uint32 span = actualTimespan;
        uint32 minSpan = TARGET_TIMESPAN / 4;
        uint32 maxSpan = TARGET_TIMESPAN * 4;

        if (span < minSpan) span = minSpan;
        if (span > maxSpan) span = maxSpan;

        uint256 newTarget = (prevTarget * span) / TARGET_TIMESPAN;
        if (newTarget == 0) newTarget = 1;
        // Max target (minimum difficulty)
        if (newTarget > 0x00000000FFFF0000000000000000000000000000000000000000000000000000) {
            newTarget = 0x00000000FFFF0000000000000000000000000000000000000000000000000000;
        }
        return newTarget;
    }

    /**
     * @notice Calculate the number of bytes needed to represent a number
     * @param x The number
     * @return n Number of bytes
     */
    function _bytesLen(uint256 x) private pure returns (uint256 n) {
        if (x == 0) return 0;
        assembly {
            n := add(div(sub(0, and(sub(x, 1), add(not(sub(x, 1)), 1))), 255), 1)
        }
    }
}
"
    },
    "contracts/src/core/BitcoinTxParser.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

library BitcoinTxParser {
    error InvalidVarInt();
    error InvalidTxLength();

    struct VarInt { uint256 value; uint256 size; }

    function _readVarInt(bytes calldata raw, uint256 offset) internal pure returns (VarInt memory varInt) {
        if (offset >= raw.length) revert InvalidVarInt();
        uint8 fb = uint8(raw[offset]);
        if (fb < 0xFD) return VarInt(fb, 1);
        if (fb == 0xFD) {
            if (offset + 3 > raw.length) revert InvalidVarInt();
            uint16 val = uint16(uint8(raw[offset+1])) | (uint16(uint8(raw[offset+2])) << 8);
            return VarInt(val, 3);
        }
        if (fb == 0xFE) {
            if (offset + 5 > raw.length) revert InvalidVarInt();
            uint32 val = uint32(uint8(raw[offset+1])) |
                        (uint32(uint8(raw[offset+2])) << 8) |
                        (uint32(uint8(raw[offset+3])) << 16) |
                        (uint32(uint8(raw[offset+4])) << 24);
            return VarInt(val, 5);
        }
        if (offset + 9 > raw.length) revert InvalidVarInt();
        uint256 val;
        unchecked {
            for (uint i = 0; i < 8; ++i) {
                val |= uint256(uint8(raw[offset + 1 + i])) << (8 * i);
            }
        }
        return VarInt(val, 9);
    }

    /**
     * @notice Parse first input from Bitcoin transaction
     * @dev Extracts prevTxId and vout from the first input
     * @param raw Raw Bitcoin transaction bytes
     * @return prevTxIdLE Previous transaction ID (little-endian)
     * @return vout Output index being spent
     */
    function parseFirstInput(bytes calldata raw) internal pure returns (bytes32 prevTxIdLE, uint32 vout) {
        require(raw.length >= 41, "InvalidTxLength");
        uint256 offset = 4;

        // Check for witness flag
        if (offset + 2 <= raw.length && raw[offset] == 0x00 && raw[offset+1] == 0x01) {
            offset += 2;
        }

        // Read input count
        VarInt memory varInt = _readVarInt(raw, offset);
        require(varInt.value >= 1, "no inputs");
        offset += varInt.size;

        // Read prevTxId (32 bytes)
        assembly {
            prevTxIdLE := calldataload(add(raw.offset, offset))
        }
        offset += 32;

        // Read vout (4 bytes, little-endian)
        vout = uint32(uint8(raw[offset])) |
               (uint32(uint8(raw[offset+1])) << 8) |
               (uint32(uint8(raw[offset+2])) << 16) |
               (uint32(uint8(raw[offset+3])) << 24);
    }

    /**
     * @notice Parse all inputs from Bitcoin transaction
     * @dev Returns arrays of all prevTxIds and vouts
     * @param raw Raw Bitcoin transaction bytes
     * @return prevTxIds Array of previous transaction IDs (little-endian)
     * @return vouts Array of output indices
     */
    function parseAllInputs(bytes calldata raw) internal pure returns (bytes32[] memory prevTxIds, uint32[] memory vouts) {
        require(raw.length >= 10, "InvalidTxLength");
        uint256 offset = 4;

        // Check for witness flag
        if (offset + 2 <= raw.length && raw[offset] == 0x00 && raw[offset+1] == 0x01) {
            offset += 2;
        }

        // Read input count
        VarInt memory vinCount = _readVarInt(raw, offset);
        offset += vinCount.size;

        prevTxIds = new bytes32[](vinCount.value);
        vouts = new uint32[](vinCount.value);

        for (uint256 i = 0; i < vinCount.value; i++) {
            // Read prevTxId (32 bytes)
            bytes32 prevTxId;
            assembly {
                prevTxId := calldataload(add(raw.offset, offset))
            }
            prevTxIds[i] = prevTxId;
            offset += 32;

            // Read vout (4 bytes, little-endian)
            uint32 vout = uint32(uint8(raw[offset])) |
                         (uint32(uint8(raw[offset+1])) << 8) |
                         (uint32(uint8(raw[offset+2])) << 16) |
                         (uint32(uint8(raw[offset+3])) << 24);
            vouts[i] = vout;
            offset += 4;

            // Skip scriptSig
            VarInt memory scriptLen = _readVarInt(raw, offset);
            offset += scriptLen.size + scriptLen.value;

            // Skip sequence
            offset += 4;
        }
    }

    function doubleSha256(bytes memory data) internal pure returns (bytes32) {
        return sha256(abi.encodePacked(sha256(data)));
    }

    function flipBytes32(bytes32 x) internal pure returns (bytes32) {
        bytes32 y;
        unchecked {
            for (uint i = 0; i < 32; ++i) {
                y |= bytes32(uint256(uint8(x[i])) << ((31 - i) * 8));
            }
        }
        return y;
    }
}
"
    },
    "contracts/src/libs/BytesUtils.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

/// @notice Утилиты для работы с bytes и calldata (LE/BE чтение, slice, reverse)
library BytesUtils {
    /// @notice Читает 4 байта (little-endian) из calldata bytes и возвращает uint32
    function toUint32LE(bytes calldata data, uint256 start) internal pure returns (uint32 v) {
        unchecked {
            v =
                (uint32(uint8(data[start + 0]))      ) |
                (uint32(uint8(data[start + 1])) <<  8) |
                (uint32(uint8(data[start + 2])) << 16) |
                (uint32(uint8(data[start + 3])) << 24);
        }
    }

    /// @notice Читает 8 байт (little-endian) из calldata bytes и возвращает uint64
    function toUint64LE(bytes calldata data, uint256 start) internal pure returns (uint64 v) {
        unchecked {
            v =
                (uint64(uint8(data[start + 0]))      ) |
                (uint64(uint8(data[start + 1])) <<  8) |
                (uint64(uint8(data[start + 2])) << 16) |
                (uint64(uint8(data[start + 3])) << 24) |
                (uint64(uint8(data[start + 4])) << 32) |
                (uint64(uint8(data[start + 5])) << 40) |
                (uint64(uint8(data[start + 6])) << 48) |
                (uint64(uint8(data[start + 7])) << 56);
        }
    }

    /// @notice Возвращает 32-байтовое значение из memory bytes по смещению (проверка OOB)
    function slice32(bytes memory bs, uint256 start) internal pure returns (bytes32 out) {
        require(bs.length >= start + 32, "BytesUtils: slice OOB");
        assembly {
            out := mload(add(add(bs, 0x20), start))
        }
    }

    /// @notice Загружает 32 байта из calldata (на offset) и возвращает bytes32
    function readBytes32Calldata(bytes calldata data, uint256 start) internal pure returns (bytes32 out) {
        require(data.length >= start + 32, "BytesUtils: read OOB");
        assembly {
            out := calldataload(add(data.offset, start))
        }
    }

    /// @notice Переворачивает порядок байт в bytes32 (endianness flip)
    function reverse32(bytes32 input) internal pure returns (bytes32 v) {
        bytes32 x = input;
        bytes32 y;
        for (uint256 i = 0; i < 32; ++i) {
            y |= (x & bytes32(uint256(0xFF) << (i * 8))) >> (i * 8) << ((31 - i) * 8);
        }
        return y;
    }

    /// @notice Конвертирует первые N байт calldata в bytes memory
    function sliceCalldata(bytes calldata data, uint256 start, uint256 len) internal pure returns (bytes memory out) {
        require(data.length >= start + len, "BytesUtils: sliceCalldata OOB");
        out = new bytes(len);
        for (uint256 i = 0; i < len; ++i) {
            out[i] = data[start + i];
        }
    }
}
"
    },
    "contracts/src/core/BitcoinHash.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

library BitcoinHash {
    function doubleSha256(bytes memory data) internal pure returns (bytes32) {
        return sha256(abi.encodePacked(sha256(data)));
    }

    function flip32(bytes32 x) internal pure returns (bytes32 y) {
        for (uint i = 0; i < 32; ++i) {
            y |= bytes32(uint256(uint8(x[i])) << ((31 - i) * 8));
        }
    }
}
"
    }
  },
  "settings": {
    "remappings": [
      "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
      "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
      "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
      "forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/",
      "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
      "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
      "openzeppelin-contracts/=lib/openzeppelin-contracts/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 1
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "prague",
    "viaIR": true
  }
}}

Tags:
Factory|addr:0x1ce76ade41a6541104168901ee81bfe196a2c729|verified:true|block:23561181|tx:0x7f5aaca453ec2f2b4b56c7df9f1d965cf0eb475eafdb77339de44d03c29a1cb6|first_check:1760293191

Submitted on: 2025-10-12 20:19:52

Comments

Log in to comment.

No comments yet.