Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/src/demo/SimpleMiningPoolV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./SimpleSPVContractV2.sol";
import "../tokens/PoolMpTokenV2.sol";
/**
* @title SimpleMiningPool
* @notice Minimal mining pool for demo - no FROST, no Factory, single owner
* @dev Simplified flow: Bitcoin rewards → MP tokens → BTC redemption
*/
contract SimpleMiningPool is Ownable, ReentrancyGuard {
// ======== STATE ========
SimpleSPVContract public immutable spvContract;
PoolMpToken public mpToken;
string public poolId;
bytes public payoutScript; // Bitcoin payout script
// Rewards tracking
struct RewardUTXO {
bytes32 txid;
uint32 vout;
uint64 amountSat;
bytes32 blockHash;
bool isRegistered;
bool isDistributed;
}
mapping(bytes32 => RewardUTXO) public rewards;
uint256 public totalRewardsRegistered;
uint256 public totalRewardsDistributed;
// Redemptions tracking
struct Redemption {
address requester;
uint64 amountSat;
bytes btcScript;
bool isConfirmed;
uint256 timestamp;
}
mapping(uint256 => Redemption) public redemptions;
uint256 public redemptionCounter;
// ======== EVENTS ========
event RewardRegistered(bytes32 indexed utxoKey, uint64 amountSat, bytes32 blockHash);
event RewardDistributed(bytes32 indexed utxoKey, address[] recipients, uint256[] amounts);
event RedemptionRequested(uint256 indexed redemptionId, address indexed requester, uint64 amountSat);
event RedemptionConfirmed(uint256 indexed redemptionId, bool approved);
// ======== CONSTRUCTOR ========
constructor(
address _spvContract,
string memory _poolId,
bytes memory _payoutScript
) Ownable(msg.sender) {
require(_spvContract != address(0), "Invalid SPV");
require(bytes(_poolId).length > 0, "Empty pool ID");
spvContract = SimpleSPVContract(_spvContract);
poolId = _poolId;
payoutScript = _payoutScript;
// Register this pool with SPV for source verification
spvContract.setPoolContract(address(this));
}
// ======== SETUP ========
function setMpToken(address _mpToken) external onlyOwner {
require(address(mpToken) == address(0), "MP token already set");
require(_mpToken != address(0), "Invalid MP token");
mpToken = PoolMpToken(_mpToken);
}
function setPayoutScript(bytes calldata _script) external onlyOwner {
payoutScript = _script;
}
// ======== REWARDS ========
function registerReward(
bytes32 txid,
uint32 vout,
uint64 amountSat,
bytes32 blockHash,
bytes32[] calldata siblings,
uint256 index
) external onlyOwner returns (bytes32 utxoKey) {
utxoKey = keccak256(abi.encodePacked(txid, vout));
require(!rewards[utxoKey].isRegistered, "Already registered");
require(amountSat > 0, "Zero amount");
// Verify block exists and is mature (6 confirmations)
require(spvContract.blockExists(blockHash), "Block not found");
require(spvContract.isMature(blockHash), "Block not mature");
// Verify transaction inclusion
require(
spvContract.checkTxInclusion(blockHash, txid, siblings, index),
"TX not in block"
);
rewards[utxoKey] = RewardUTXO({
txid: txid,
vout: vout,
amountSat: amountSat,
blockHash: blockHash,
isRegistered: true,
isDistributed: false
});
totalRewardsRegistered += amountSat;
// Register this UTXO in SPV for source verification
spvContract.registerPoolUTXO(txid, vout);
emit RewardRegistered(utxoKey, amountSat, blockHash);
}
function distributeReward(
bytes32 utxoKey,
address[] calldata recipients,
uint256[] calldata amounts
) external onlyOwner nonReentrant {
require(address(mpToken) != address(0), "MP token not set");
require(recipients.length == amounts.length, "Length mismatch");
require(recipients.length > 0, "No recipients");
RewardUTXO storage reward = rewards[utxoKey];
require(reward.isRegistered, "Not registered");
require(!reward.isDistributed, "Already distributed");
// Verify total amount matches
uint256 totalAmount = 0;
for (uint256 i = 0; i < amounts.length; i++) {
totalAmount += amounts[i];
}
require(totalAmount == reward.amountSat, "Amount mismatch");
// Mint MP tokens to recipients
for (uint256 i = 0; i < recipients.length; i++) {
if (amounts[i] > 0) {
mpToken.mint(recipients[i], amounts[i]);
}
}
reward.isDistributed = true;
totalRewardsDistributed += reward.amountSat;
emit RewardDistributed(utxoKey, recipients, amounts);
}
// ======== REDEMPTIONS ========
function requestRedemption(
uint64 amountSat,
bytes calldata btcScript
) external nonReentrant returns (uint256 redemptionId) {
require(address(mpToken) != address(0), "MP token not set");
require(amountSat > 0, "Zero amount");
require(btcScript.length > 0, "Empty script");
// Burn MP tokens
mpToken.burn(msg.sender, amountSat);
redemptionId = redemptionCounter++;
redemptions[redemptionId] = Redemption({
requester: msg.sender,
amountSat: amountSat,
btcScript: btcScript,
isConfirmed: false,
timestamp: block.timestamp
});
emit RedemptionRequested(redemptionId, msg.sender, amountSat);
}
function confirmRedemption(uint256 redemptionId, bool approved) external onlyOwner {
Redemption storage redemption = redemptions[redemptionId];
require(redemption.amountSat > 0, "Redemption not found");
require(!redemption.isConfirmed, "Already confirmed");
redemption.isConfirmed = true;
emit RedemptionConfirmed(redemptionId, approved);
}
// ======== VIEWS ========
function getReward(bytes32 utxoKey) external view returns (RewardUTXO memory) {
return rewards[utxoKey];
}
function getRedemption(uint256 redemptionId) external view returns (Redemption memory) {
return redemptions[redemptionId];
}
function getMpTokenBalance(address account) external view returns (uint256) {
if (address(mpToken) == address(0)) return 0;
return mpToken.balanceOf(account);
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {StorageSlot} from "./StorageSlot.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*
* IMPORTANT: Deprecated. This storage-based reentrancy guard will be removed and replaced
* by the {ReentrancyGuardTransient} variant in v6.0.
*/
abstract contract ReentrancyGuard {
using StorageSlot for bytes32;
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant REENTRANCY_GUARD_STORAGE =
0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
/**
* @dev A `view` only version of {nonReentrant}. Use to block view functions
* from being called, preventing reading from inconsistent contract state.
*
* CAUTION: This is a "view" modifier and does not change the reentrancy
* status. Use it only on view functions. For payable or non-payable functions,
* use the standard {nonReentrant} modifier instead.
*/
modifier nonReentrantView() {
_nonReentrantBeforeView();
_;
}
function _nonReentrantBeforeView() private view {
if (_reentrancyGuardEntered()) {
revert ReentrancyGuardReentrantCall();
}
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
_nonReentrantBeforeView();
// Any calls to nonReentrant after this point will fail
_reentrancyGuardStorageSlot().getUint256Slot().value = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _reentrancyGuardStorageSlot().getUint256Slot().value == ENTERED;
}
function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
return REENTRANCY_GUARD_STORAGE;
}
}
"
},
"contracts/src/demo/SimpleSPVContractV2.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/tokens/PoolMpTokenV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title PoolMpToken
* @notice Пул-специфичный mp-токен (например, mpBTC-pool001).
* Минт/бёрн — только DAO пула. Неизменяемо знает адрес DAO пула.
* Опционально может ограничивать трансферы (внутрипуловые переводы по вайтлисту).
*/
contract PoolMpToken is ERC20, ERC20Permit, AccessControl {
// Роли (назначаются DAO пула в конструкторе)
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
bytes32 public constant WHITELIST_MANAGER = keccak256("WHITELIST_MANAGER");
bytes32 public constant WHITELISTED_ACCOUNT = keccak256("WHITELISTED_ACCOUNT");
/// @notice DAO пула, которому принадлежат админ-права
address public immutable poolDAO;
/// @notice Если true — включены ограничения трансферов (только вайтлист/DAO)
bool public immutable restrictTransfer;
event WhitelistUpdated(address indexed account, bool allowed);
constructor(
string memory name_,
string memory symbol_,
address poolDAO_,
bool restrictTransfer_
) ERC20(name_, symbol_) ERC20Permit(name_) {
require(poolDAO_ != address(0), "poolDAO=0");
poolDAO = poolDAO_;
restrictTransfer = restrictTransfer_;
// Делегируем полный контроль DAO пула
_grantRole(DEFAULT_ADMIN_ROLE, poolDAO_);
_grantRole(MINTER_ROLE, poolDAO_);
_grantRole(BURNER_ROLE, poolDAO_);
_grantRole(WHITELIST_MANAGER, poolDAO_);
}
// -------------------
// DAO-ограниченные API
// -------------------
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) {
_burn(from, amount);
}
/// @notice DAO (или делегаты с ролью) управляют вайтлистом получателей/отправителей
function addToWhitelist(address account) external onlyRole(WHITELIST_MANAGER) {
_grantRole(WHITELISTED_ACCOUNT, account);
emit WhitelistUpdated(account, true);
}
function removeFromWhitelist(address account) external onlyRole(WHITELIST_MANAGER) {
_revokeRole(WHITELISTED_ACCOUNT, account);
emit WhitelistUpdated(account, false);
}
// -------------------
// Ограничения трансфера
// -------------------
/**
* @dev Если включён режим restrictTransfer:
* - mint/burn всегда разрешены (from==0 / to==0)
* - любые переводы разрешены DAO пула
* - иначе обе стороны должны быть в вайтлисте (или одна из них — DAO пула)
*/
function _update(address from, address to, uint256 value) internal override {
if (restrictTransfer) {
bool minting = from == address(0);
bool burning = to == address(0);
if (!(minting || burning)) {
if (from != poolDAO && to != poolDAO) {
// проверяем обе стороны
require(
hasRole(WHITELISTED_ACCOUNT, from) &&
hasRole(WHITELISTED_ACCOUNT, to),
"mp: restricted transfer"
);
}
}
}
super._update(from, to, value);
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct Int256Slot {
int256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
/**
* @dev Returns a `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
}
"
},
"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;
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/// @inheritdoc IERC20
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/// @inheritdoc IERC20
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/// @inheritdoc IERC20
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner`'s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/ERC20Permit.sol)
pragma solidity ^0.8.24;
import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";
/**
* @dev Implementation of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC-20 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.
*/
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
bytes32 private constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev Permit deadline has expired.
*/
error ERC2612ExpiredSignature(uint256 deadline);
/**
* @dev Mismatched signature.
*/
error ERC2612InvalidSigner(address signer, address owner);
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC-20 token name.
*/
constructor(string memory name) EIP712(name, "1") {}
/// @inheritdoc IERC20Permit
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}
bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}
_approve(owner, spender, value);
}
/// @inheritdoc IERC20Permit
function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
/// @inheritdoc IERC20Permit
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
return _domainSeparatorV4();
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/AccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {IERC165, ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(M
Submitted on: 2025-10-26 13:05:21
Comments
Log in to comment.
No comments yet.