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
}
}}
Submitted on: 2025-10-12 20:19:52
Comments
Log in to comment.
No comments yet.