Contract Name:
OneStepDeploy
Contract Source Code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Operation} from "../contracts/Common.sol";
import {FBTC} from "../contracts/FBTC.sol";
import {FireBridge, ChainCode} from "../contracts/FireBridge.sol";
import {FBTCMinter} from "../contracts/FBTCMinter.sol";
import {FeeModel} from "../contracts/FeeModel.sol";
import {FBTCGovernorModule} from "../contracts/FBTCGovernorModule.sol";
interface IFactory {
function deploy(
uint8 typ,
bytes32 salt,
bytes memory initCode
) external returns (address);
}
library FactoryLib {
function doDeploy(
IFactory factory,
uint256 salt,
bytes memory code
) internal returns (address) {
return factory.deploy(3, bytes32(salt), code);
}
}
struct DeployConfig {
address factory;
bytes32 tag;
bytes32 mainChain;
address owner;
address feeRecipientAndUpdater;
// Minter
address mintOperator;
address burnOperator;
address crosschainOperator;
// GovernorModule
address[] pauserAndLockers;
address userManager;
address chainMananger;
// Contract code.
bytes fireBridgeCode;
bytes proxyCode;
bytes fbtcCode;
bytes feeModelCode;
bytes minterCode;
bytes governorModuleCode;
bytes32[] dstChains;
}
contract OneStepDeploy {
using FactoryLib for IFactory;
function deploy(DeployConfig memory c) external {
IFactory factory = IFactory(c.factory);
uint256 saltStart = uint256(c.tag);
address owner = c.owner;
address tempOwner = address(this);
///////////////////////////
// FireBridge
address impl = factory.doDeploy(
saltStart++,
abi.encodePacked(c.fireBridgeCode, abi.encode(owner, c.mainChain))
);
address bridgeAddress = factory.doDeploy(
saltStart++,
abi.encodePacked(
c.proxyCode,
abi.encode(
impl,
abi.encodeCall(FireBridge.initialize, (tempOwner))
)
)
);
FireBridge bridge = FireBridge(bridgeAddress);
bridge.addDstChains(c.dstChains);
bridge.setFeeRecipient(c.feeRecipientAndUpdater);
bridge.transferOwnership(owner);
///////////////////////////
// FeeModel
FeeModel feeModel = FeeModel(
factory.doDeploy(
saltStart++, // Updated
abi.encodePacked(c.feeModelCode, abi.encode(tempOwner))
)
);
feeModel.transferOwnership(owner);
bridge.setFeeModel(address(feeModel));
// Cross-chain fee: Min 0.0001 FBTC, Rate 0.01%
uint32 FEE_RATE_BASE = feeModel.FEE_RATE_BASE();
FeeModel.FeeTier[] memory tiers = new FeeModel.FeeTier[](1);
tiers[0] = FeeModel.FeeTier(
type(uint224).max,
FEE_RATE_BASE / 10000 // 0.01%
);
FeeModel.FeeConfig memory _config = FeeModel.FeeConfig(
type(uint256).max,
0.0001 * 1e8, // 0.0001 FBTC
tiers
);
feeModel.setDefaultFeeConfig(Operation.CrosschainRequest, _config);
// No mint fee
_config.minFee = 0;
tiers[0].feeRate = 0;
feeModel.setDefaultFeeConfig(Operation.Mint, _config);
// Burn fee:
_config.minFee = 0.01 * 1e8; // 0.01 BTC
_config.tiers = new FeeModel.FeeTier[](4);
uint224 BTC_UNIT = 1e8;
// < 200 BTC, 0.20%
_config.tiers[0] = FeeModel.FeeTier(
200 * BTC_UNIT,
(FEE_RATE_BASE * 20) / 10000
);
// >=200 <500 BTC, 0.16%
_config.tiers[1] = FeeModel.FeeTier(
500 * BTC_UNIT,
(FEE_RATE_BASE * 16) / 10000
);
// >=500 <1000 BTC, 0.12%
_config.tiers[2] = FeeModel.FeeTier(
1000 * BTC_UNIT,
(FEE_RATE_BASE * 12) / 10000
);
// >=1000 BTC, 0.10%
_config.tiers[3] = FeeModel.FeeTier(
type(uint224).max,
(FEE_RATE_BASE * 10) / 10000
);
feeModel.setDefaultFeeConfig(Operation.Burn, _config);
///////////////////////////
// FBTC
address fbtcAddress = factory.doDeploy(
saltStart++,
abi.encodePacked(c.fbtcCode, abi.encode(owner, bridgeAddress))
);
bridge.setToken(fbtcAddress);
///////////////////////////
// FBTCMinter
FBTCMinter minter = FBTCMinter(
factory.doDeploy(
saltStart++,
abi.encodePacked(
c.minterCode,
abi.encode(tempOwner, bridgeAddress)
)
)
);
minter.transferOwnership(owner);
bridge.setMinter(address(minter));
minter.grantRole(minter.MINT_ROLE(), c.mintOperator);
minter.grantRole(minter.BURN_ROLE(), c.burnOperator);
minter.grantRole(minter.CROSSCHAIN_ROLE(), c.crosschainOperator);
///////////////////////////
// GovermentModule
FBTCGovernorModule gov = FBTCGovernorModule(
factory.doDeploy(
saltStart++,
abi.encodePacked(
c.governorModuleCode,
abi.encode(tempOwner, fbtcAddress)
)
)
);
gov.transferOwnership(owner);
bytes32 FBTC_PAUSER_ROLE = gov.FBTC_PAUSER_ROLE();
bytes32 LOCKER_ROLE = gov.LOCKER_ROLE();
bytes32 BRIDGE_PAUSER_ROLE = gov.BRIDGE_PAUSER_ROLE();
for (uint i = 0; i < c.pauserAndLockers.length; ++i) {
address pauserAndLocker = c.pauserAndLockers[i];
gov.grantRole(FBTC_PAUSER_ROLE, pauserAndLocker);
gov.grantRole(LOCKER_ROLE, pauserAndLocker);
gov.grantRole(BRIDGE_PAUSER_ROLE, pauserAndLocker);
}
gov.grantRole(gov.USER_MANAGER_ROLE(), c.userManager);
gov.grantRole(gov.CHAIN_MANAGER_ROLE(), c.chainMananger);
gov.grantRole(gov.FEE_UPDATER_ROLE(), c.feeRecipientAndUpdater);
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
enum Operation {
Nop, // starts from 1.
Mint,
Burn,
CrosschainRequest,
CrosschainConfirm
}
enum Status {
Unused,
Pending,
Confirmed,
Rejected
}
struct Request {
Operation op;
Status status;
uint128 nonce; // Those can be packed into one slot in evm storage.
bytes32 srcChain;
bytes srcAddress;
bytes32 dstChain;
bytes dstAddress;
uint256 amount; // Transfer value without fee.
uint256 fee;
bytes extra;
}
struct UserInfo {
bool locked;
string depositAddress;
string withdrawalAddress;
}
library ChainCode {
// For EVM chains, the chain code is chain id in bytes32 format.
bytes32 constant ETH =
0x0000000000000000000000000000000000000000000000000000000000000001;
bytes32 constant MANTLE =
0x0000000000000000000000000000000000000000000000000000000000001388;
// Other chains.
bytes32 constant BTC =
0x0100000000000000000000000000000000000000000000000000000000000000;
bytes32 constant SOLANA =
0x0200000000000000000000000000000000000000000000000000000000000000;
// For test.
bytes32 constant XTN =
0x0110000000000000000000000000000000000000000000000000000000000000;
function getSelfChainCode() internal view returns (bytes32) {
return bytes32(block.chainid);
}
}
library RequestLib {
/// @dev This request hash should be unique across all chains.
/// op nonce srcChain dstChain
/// (1) Mint: 1 nonce BTC chainid ...
/// (2) Burn: 2 nonce chainid BTC ...
/// (3) Cross: 3 nonce chainid dst ...
/// (4) Cross confirm: 4 nonce src chainid ...
/// On the same chain:
/// The `nonce` differs
/// On different chains:
/// The `chain.id` differs or the `op` differs
function getRequestHash(
Request memory r
) internal pure returns (bytes32 _hash) {
_hash = keccak256(
abi.encode(
r.op,
r.nonce,
r.srcChain,
r.srcAddress,
r.dstChain,
r.dstAddress,
r.amount,
r.fee,
r.extra // For Burn, this should be none.
)
);
}
function getCrossSourceRequestHash(
Request memory src
) internal pure returns (bytes32 _hash) {
// Save.
bytes memory extra = src.extra;
// Set temperary data to calculate hash.
src.op = Operation.CrosschainRequest;
src.extra = ""; // clear
_hash = getRequestHash(src);
// Restore.
src.op = Operation.CrosschainConfirm;
src.extra = extra;
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {FToken} from "./base/FToken.sol";
contract FBTC is FToken {
constructor(address _owner, address _bridge) {
initialize(_owner, _bridge);
}
function initialize(address _owner, address _bridge) public initializer {
__FToken_init(_owner, _bridge, "Fire Bitcoin", "FBTC");
}
function decimals() public pure override returns (uint8) {
return 8;
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Request, UserInfo, RequestLib, Operation, Status, ChainCode} from "./Common.sol";
import {BridgeStorage} from "./base/BridgeStorage.sol";
import {FToken} from "./base/FToken.sol";
import {BasePausableUpgradeable} from "./base/BasePausableUpgradeable.sol";
import {FeeModel} from "./FeeModel.sol";
contract FireBridge is BridgeStorage, BasePausableUpgradeable {
using RequestLib for Request;
using EnumerableSet for EnumerableSet.AddressSet;
using EnumerableSet for EnumerableSet.Bytes32Set;
event QualifiedUserAdded(
address indexed _user,
string _depositAddress,
string _withdrawalAddress
);
event QualifiedUserEdited(
address indexed _user,
string _depositAddress,
string _withdrawalAddress
);
event QualifiedUserLocked(address indexed _user);
event QualifiedUserUnlocked(address indexed _user);
event QualifiedUserRemoved(address indexed _user);
event TokenSet(address indexed _token);
event MinterSet(address indexed _minter);
event FeeModelSet(address indexed _feeModel);
event FeeRecipientSet(address indexed _feeRecipient);
event DepositTxBlocked(
bytes32 indexed _depositTxid,
uint256 indexed _outputIndex
);
event RequestAdded(bytes32 indexed _hash, Operation indexed op, Request _r);
event RequestConfirmed(bytes32 indexed _hash);
event FeePaid(address indexed _feeRecipient, uint256 indexed _feeAmount);
event DstChainAdded(bytes32 indexed _dstChain);
event DstChainRemoved(bytes32 indexed _dstChain);
modifier onlyMinter() {
require(msg.sender == minter, "Caller not minter");
_;
}
modifier onlyActiveQualifiedUser() {
require(isQualifiedUser(msg.sender), "Caller not qualified");
require(!userInfo[msg.sender].locked, "Caller locked");
_;
}
bytes32 public immutable MAIN_CHAIN;
constructor(address _owner, bytes32 _mainChain) {
initialize(_owner);
MAIN_CHAIN = _mainChain;
}
function initialize(address _owner) public initializer {
__BasePausableUpgradeable_init(_owner);
}
function _splitFeeAndUpdate(Request memory r) internal view {
uint256 _fee = FeeModel(feeModel).getFee(r);
r.fee = _fee;
r.amount = r.amount - _fee;
}
function _payFee(uint256 _fee, bool viaMint) internal {
if (_fee == 0) return;
address _feeRecipient = feeRecipient;
if (viaMint) {
FToken(fbtc).mint(_feeRecipient, _fee);
} else {
FToken(fbtc).payFee(msg.sender, _feeRecipient, _fee);
}
emit FeePaid(_feeRecipient, _fee);
}
function _addRequest(Request memory r) internal returns (bytes32 _hash) {
require(
r.nonce == requestHashes.length,
"Fatal: nonce not equals array length"
); // Should never happen.
_hash = r.getRequestHash();
// For CrosschainRequest: update extra with self hash.
if (r.op == Operation.CrosschainRequest) {
r.extra = abi.encode(_hash);
}
requestHashes.push(_hash);
requests[_hash] = r;
emit RequestAdded(_hash, r.op, r);
}
/// Owner methods.
/// Qualified user management.
function addQualifiedUser(
address _user,
string calldata _depositAddress,
string calldata _withdrawalAddress
) external onlyOwner {
require(qualifiedUsers.add(_user), "User already qualified");
require(
depositAddressToUser[_depositAddress] == address(0),
"Deposit address used"
);
userInfo[_user] = UserInfo(false, _depositAddress, _withdrawalAddress);
depositAddressToUser[_depositAddress] = _user;
emit QualifiedUserAdded(_user, _depositAddress, _withdrawalAddress);
}
function editQualifiedUser(
address _user,
string calldata _depositAddress,
string calldata _withdrawalAddress
) external onlyOwner {
require(isQualifiedUser(_user), "User not qualified");
require(!userInfo[_user].locked, "User locked");
string memory _oldDepositAddress = userInfo[_user].depositAddress;
if (
keccak256(bytes(_depositAddress)) !=
keccak256(bytes(_oldDepositAddress))
) {
require(
depositAddressToUser[_depositAddress] == address(0),
"Deposit address used"
);
delete depositAddressToUser[_oldDepositAddress];
userInfo[_user].depositAddress = _depositAddress;
depositAddressToUser[_depositAddress] = _user;
}
userInfo[_user].withdrawalAddress = _withdrawalAddress;
emit QualifiedUserEdited(_user, _depositAddress, _withdrawalAddress);
}
function removeQualifiedUser(address _qualifiedUser) external onlyOwner {
require(qualifiedUsers.remove(_qualifiedUser), "User not qualified");
string memory _depositAddress = userInfo[_qualifiedUser].depositAddress;
delete depositAddressToUser[_depositAddress];
delete userInfo[_qualifiedUser];
emit QualifiedUserRemoved(_qualifiedUser);
}
function lockQualifiedUser(address _qualifiedUser) external onlyOwner {
require(isQualifiedUser(_qualifiedUser), "User not qualified");
require(!userInfo[_qualifiedUser].locked, "User already locked");
userInfo[_qualifiedUser].locked = true;
emit QualifiedUserLocked(_qualifiedUser);
}
function unlockQualifiedUser(address _qualifiedUser) external onlyOwner {
require(isQualifiedUser(_qualifiedUser), "User not qualified");
require(userInfo[_qualifiedUser].locked, "User not locked");
userInfo[_qualifiedUser].locked = false;
emit QualifiedUserUnlocked(_qualifiedUser);
}
/// Protocol configuration.
function setToken(address _token) external onlyOwner {
fbtc = _token;
emit TokenSet(_token);
}
function setMinter(address _minter) external onlyOwner {
minter = _minter;
emit MinterSet(_minter);
}
function setFeeModel(address _feeModel) external onlyOwner {
require(_feeModel != address(0), "Invalid feeModel");
feeModel = _feeModel;
emit FeeModelSet(_feeModel);
}
function setFeeRecipient(address _feeRecipient) external onlyOwner {
require(_feeRecipient != address(0), "Invalid feeRecipient");
feeRecipient = _feeRecipient;
emit FeeRecipientSet(_feeRecipient);
}
function addDstChains(bytes32[] memory _dstChains) external onlyOwner {
for (uint i = 0; i < _dstChains.length; i++) {
bytes32 _dstChain = _dstChains[i];
require(
_dstChain != MAIN_CHAIN && _dstChain != chain(),
"Invalid dst chain"
);
if (dstChains.add(_dstChain)) {
emit DstChainAdded(_dstChain);
}
}
}
function removeDstChains(bytes32[] memory _dstChains) external onlyOwner {
for (uint i = 0; i < _dstChains.length; i++) {
bytes32 _dstChain = _dstChains[i];
if (dstChains.remove(_dstChain)) {
emit DstChainRemoved(_dstChain);
}
}
}
/// @notice Mark the deposit tx invalid and reject the minting request if any.
function blockDepositTx(
bytes32 _depositTxid,
uint256 _outputIndex
) external onlyOwner {
bytes32 REJECTED = bytes32(uint256(0xdead));
bytes memory _depositTxData = abi.encode(_depositTxid, _outputIndex);
bytes32 depositDataHash = keccak256(_depositTxData);
bytes32 requestHash = usedDepositTxs[depositDataHash];
require(requestHash == bytes32(0), "Already confirmed or blocked");
// Mark it as rejected.
usedDepositTxs[depositDataHash] = REJECTED;
emit DepositTxBlocked(_depositTxid, _outputIndex);
}
/// QualifiedUser methods.
/// @notice Initiate a FBTC minting request for the qualifiedUser.
/// @param _amount The amount of FBTC to mint.
/// @param _depositTxid The BTC deposit txid
/// @param _outputIndex The transaction output index to user's deposit address.
/// @return _hash The hash of the new created request.
/// @return _r The full new created request.
function addMintRequest(
uint256 _amount,
bytes32 _depositTxid,
uint256 _outputIndex
)
external
onlyActiveQualifiedUser
whenNotPaused
returns (bytes32 _hash, Request memory _r)
{
// Check request.
require(_amount > 0, "Invalid amount");
require(uint256(_depositTxid) != 0, "Empty deposit txid");
bytes memory _depositTxData = abi.encode(_depositTxid, _outputIndex);
bytes32 depositDataHash = keccak256(_depositTxData);
require(
usedDepositTxs[depositDataHash] == bytes32(uint256(0)),
"Used BTC deposit tx"
);
// Compose request. Main -> Self
_r = Request({
nonce: nonce(),
op: Operation.Mint,
srcChain: MAIN_CHAIN,
srcAddress: bytes(userInfo[msg.sender].depositAddress),
dstChain: chain(),
dstAddress: abi.encode(msg.sender),
amount: _amount,
fee: 0, // To be set in `_splitFeeAndUpdate`
extra: _depositTxData,
status: Status.Pending
});
// Split fee.
_splitFeeAndUpdate(_r);
// Save request.
_hash = _addRequest(_r);
}
/// @notice Initiate a FBTC burning request for the qualifiedUser.
/// @param _amount The amount of FBTC to burn.
/// @return _hash The hash of the new created request.
/// @return _r The full new created request.
function addBurnRequest(
uint256 _amount
)
external
onlyActiveQualifiedUser
whenNotPaused
returns (bytes32 _hash, Request memory _r)
{
// Check request.
require(_amount > 0, "Invalid amount");
// Compose request. Self -> Main
_r = Request({
nonce: nonce(),
op: Operation.Burn,
srcChain: chain(),
srcAddress: abi.encode(msg.sender),
dstChain: MAIN_CHAIN,
dstAddress: bytes(userInfo[msg.sender].withdrawalAddress),
amount: _amount,
fee: 0, // To be set in `_splitFeeAndUpdate`
extra: "", // Unset until confirmed
status: Status.Pending
});
// Split fee.
_splitFeeAndUpdate(_r);
// Save request.
_hash = _addRequest(_r);
// Pay fee
_payFee(_r.fee, false);
// Burn tokens.
FToken(fbtc).burn(msg.sender, _r.amount);
}
/// Customer methods.
/// @notice Initiate a FBTC cross-chain bridging request.
/// @param _targetChain The target chain identifier.
/// @param _targetAddress The encoded address on the target chain .
/// @param _amount The amount of FBTC to cross-chain.
/// @return _hash The hash of the new created request.
/// @return _r The full new created request.
function addCrosschainRequest(
bytes32 _targetChain,
bytes memory _targetAddress,
uint256 _amount
) public whenNotPaused returns (bytes32 _hash, Request memory _r) {
// Check request.
require(_amount > 0, "Invalid amount");
require(dstChains.contains(_targetChain), "Target chain not allowed");
// Compose request. Self -> Target
_r = Request({
nonce: nonce(),
op: Operation.CrosschainRequest,
srcChain: chain(),
srcAddress: abi.encode(msg.sender),
amount: _amount,
dstChain: _targetChain,
dstAddress: _targetAddress,
fee: 0,
extra: "", // Not include in hash.
status: Status.Unused // Not used.
});
// Split fee.
_splitFeeAndUpdate(_r);
// Save request.
_hash = _addRequest(_r);
// Pay fee
_payFee(_r.fee, false);
// Burn tokens.
FToken(fbtc).burn(msg.sender, _r.amount);
}
/// @notice A more user-friendly interface to cross-chain to an
/// EVM-compatible target chain.
/// @param _targetChainId The chain id of target EVM chain.
/// @param _targetAddress The target EVM address.
/// @param _amount The amount of FBTC to cross-chain.
/// @return _hash The hash of the new created request.
/// @return _r The full new created request.
function addEVMCrosschainRequest(
uint256 _targetChainId,
address _targetAddress,
uint256 _amount
) external returns (bytes32 _hash, Request memory _r) {
return
addCrosschainRequest(
bytes32(_targetChainId),
abi.encode(_targetAddress),
_amount
);
}
/// Minter methods.
/// @notice Confirm the minting request.
/// @param _hash The minting request hash.
function confirmMintRequest(
bytes32 _hash
) external onlyMinter whenNotPaused {
// Check request.
Request storage r = requests[_hash];
require(r.op == Operation.Mint, "Not Mint request");
uint256 _amount = r.amount;
require(_amount > 0, "Invalid request amount");
require(r.status == Status.Pending, "Invalid request status");
// Check and update deposit data usage status.
bytes32 depositDataHash = keccak256(r.extra);
require(
usedDepositTxs[depositDataHash] == bytes32(uint256(0)),
"Used BTC deposit tx"
);
usedDepositTxs[depositDataHash] = _hash;
// Update status.
r.status = Status.Confirmed;
emit RequestConfirmed(_hash);
// Mint tokens
FToken(fbtc).mint(abi.decode(r.dstAddress, (address)), _amount);
// Pay fee.
_payFee(r.fee, true);
}
/// @notice Confirm the burning request.
/// @param _hash The burning request id.
/// @param _withdrawalTxid The BTC withdrawal txid
/// @param _outputIndex The transaction output index to user's withdrawal address.
function confirmBurnRequest(
bytes32 _hash,
bytes32 _withdrawalTxid,
uint256 _outputIndex
) external onlyMinter whenNotPaused {
// Check request.
require(uint256(_withdrawalTxid) != 0, "Empty withdraw txid");
Request storage r = requests[_hash];
require(r.op == Operation.Burn, "Not Burn request");
require(r.amount > 0, "Invalid request amount");
require(r.status == Status.Pending, "Invalid request status");
bytes memory _withdrawalTxData = abi.encode(
_withdrawalTxid,
_outputIndex
);
bytes32 _withdrawalDataHash = keccak256(_withdrawalTxData);
require(
usedWithdrawalTxs[_withdrawalDataHash] == bytes32(uint256(0)),
"Used BTC withdrawal tx"
);
usedWithdrawalTxs[_withdrawalDataHash] = _hash;
// Update status.
r.status = Status.Confirmed;
r.extra = _withdrawalTxData;
emit RequestConfirmed(_hash);
}
/// @notice Confirm the cross-chain request.
/// @dev Most fields of the request should be the same as the one on
/// source chain. Note:
/// 1. The `op` is changed to `CrosschainConfirm`
/// 2. The `nonce` is the source nonce, used to calculate source request hash.
/// 3. The `status` should be `Unused` (0).
/// 4. The `extra` should be 32 bytes length and contain the source request hash.
/// @param r The full cross-chain request.
function confirmCrosschainRequest(
Request memory r
) external onlyMinter whenNotPaused {
// Check request.
require(r.amount > 0, "Invalid request amount");
require(r.dstChain == chain(), "Dst chain not match");
require(
r.op == Operation.CrosschainConfirm,
"Not CrosschainConfirm request"
);
require(r.status == Status.Unused, "Status should not be used");
require(r.extra.length == 32, "Invalid extra: not valid bytes32");
require(
r.dstAddress.length == 32,
"Invalid dstAddress: not 32 bytes length"
);
require(
abi.decode(r.dstAddress, (uint256)) <= type(uint160).max,
"Invalid dstAddress: not address"
);
bytes32 srcHash = abi.decode(r.extra, (bytes32));
// Set to request to calc hash.
require(
r.getCrossSourceRequestHash() == srcHash,
"Source request hash is incorrect"
);
require(
crosschainRequestConfirmation[srcHash] == bytes32(0),
"Source request already confirmed"
);
// Save request.
r.nonce = nonce(); // Override src nonce to dst nonce.
bytes32 _dsthash = _addRequest(r);
crosschainRequestConfirmation[srcHash] = _dsthash;
// Mint tokens.
FToken(fbtc).mint(abi.decode(r.dstAddress, (address)), r.amount);
}
/// View functions.
/// @notice The unique chain identifier in FBTC system.
function chain() public view returns (bytes32) {
return ChainCode.getSelfChainCode();
}
/// @notice The next request id
function nonce() public view returns (uint128) {
require(
requestHashes.length < type(uint128).max,
"Fatal: nonce overflow"
); // Unlikely happens.
return uint128(requestHashes.length);
}
/// @notice Check whether the address is qualified
function isQualifiedUser(address _user) public view returns (bool) {
return qualifiedUsers.contains(_user);
}
/// @notice Check whether the address is qualified and active
function isActiveUser(address _user) public view returns (bool) {
return isQualifiedUser(_user) && !userInfo[_user].locked;
}
/// @notice Get all qualified users
function getQualifiedUsers() external view returns (address[] memory) {
return qualifiedUsers.values();
}
/// @notice Get all active users
function getActiveUsers() external view returns (address[] memory _users) {
uint256 activeCount = 0;
for (uint256 i = 0; i < qualifiedUsers.length(); ++i) {
UserInfo storage info = userInfo[qualifiedUsers.at(i)];
if (!info.locked) {
activeCount += 1;
}
}
_users = new address[](activeCount);
uint256 j = 0;
for (uint256 i = 0; i < qualifiedUsers.length(); ++i) {
address _user = qualifiedUsers.at(i);
UserInfo storage info = userInfo[_user];
if (!info.locked) {
_users[j++] = _user;
}
}
}
/// @notice Get qualified user information.
function getQualifiedUserInfo(
address _user
) external view returns (UserInfo memory info) {
info = userInfo[_user];
}
/// @notice Get qualified user information.
function getValidDstChains() external view returns (bytes32[] memory) {
return dstChains.values();
}
/// @notice Get request by the index a.k.a the id.
/// @param _id The index.
/// @return r The returned request.
function getRequestById(
uint256 _id
) external view returns (Request memory r) {
require(_id < requestHashes.length, "Request not exists");
r = requests[requestHashes[_id]];
}
/// @notice Get multiple requests by an id range.
/// @param _start The start index.
/// @param _end The end index.
/// @return rs The returned requests.
function getRequestsByIdRange(
uint256 _start,
uint256 _end
) external view returns (Request[] memory rs) {
uint256 maxIndex = requestHashes.length - 1;
if (_end > maxIndex) _end = maxIndex;
require(_start <= _end, "start should <= end");
uint256 len = _end - _start + 1;
rs = new Request[](len);
for (uint i = 0; i < len; i++) {
rs[i] = requests[requestHashes[i + _start]];
}
}
/// @notice Get request by hash
/// @param _hash The hash
/// @return r The returned request.
function getRequestByHash(
bytes32 _hash
) public view returns (Request memory r) {
r = requests[_hash];
require(r.op != Operation.Nop, "Request not exists");
}
/// @notice Get multiple requests by hashes.
/// @param _hashes The hash list
/// @return rs The returned requests.
function getRequestsByHashes(
bytes32[] calldata _hashes
) external view returns (Request[] memory rs) {
rs = new Request[](_hashes.length);
for (uint i = 0; i < _hashes.length; i++) {
rs[i] = getRequestByHash(_hashes[i]);
}
}
/// @notice Calculate hash of the request.
/// @param _r The Request to hash
/// @return _hash The hash result
function calculateRequestHash(
Request memory _r
) external pure returns (bytes32 _hash) {
_hash = _r.getRequestHash();
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {Request} from "./Common.sol";
import {FireBridge} from "./FireBridge.sol";
import {RoleBasedAccessControl} from "./base/RoleBasedAccessControl.sol";
contract FBTCMinter is RoleBasedAccessControl {
FireBridge public bridge;
bytes32 public constant MINT_ROLE = "1_mint";
bytes32 public constant BURN_ROLE = "2_burn";
bytes32 public constant CROSSCHAIN_ROLE = "3_crosschain";
event BridgeUpdated(address indexed newBridge, address indexed oldBridge);
constructor(address _owner, address _bridge) {
initialize(_owner, _bridge);
}
function initialize(address _owner, address _bridge) public initializer {
__BaseOwnableUpgradeable_init(_owner);
bridge = FireBridge(_bridge);
}
function setBridge(address _bridge) external onlyOwner {
address oldBridge = address(bridge);
bridge = FireBridge(_bridge);
emit BridgeUpdated(_bridge, oldBridge);
}
/// Operator methods.
function confirmMintRequest(bytes32 _hash) external onlyRole(MINT_ROLE) {
bridge.confirmMintRequest(_hash);
}
function confirmBurnRequest(
bytes32 _hash,
bytes32 _withdrawalTxid,
uint256 _outputIndex
) external onlyRole(BURN_ROLE) {
bridge.confirmBurnRequest(_hash, _withdrawalTxid, _outputIndex);
}
function confirmCrosschainRequest(
Request calldata r
) external onlyRole(CROSSCHAIN_ROLE) {
bridge.confirmCrosschainRequest(r);
}
function batchConfirmCrosschainRequest(
Request[] calldata rs
) external onlyRole(CROSSCHAIN_ROLE) {
for (uint256 i = 0; i < rs.length; i++) {
bridge.confirmCrosschainRequest(rs[i]);
}
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {Operation, Request} from "./Common.sol";
import {BaseOwnableUpgradeable} from "./base/BaseOwnableUpgradeable.sol";
contract FeeModel is BaseOwnableUpgradeable {
uint32 public constant FEE_RATE_BASE = 1_000_000;
struct FeeTier {
uint224 amountTier; // Amount tier.
uint32 feeRate; // Fee rate.
}
struct FeeConfig {
uint256 maxFee; // Maximum fee.
uint256 minFee; // Minimum fee.
FeeTier[] tiers; // Order by amount asc.
}
mapping(Operation op => FeeConfig cfg) defaultFeeConfig;
mapping(bytes32 dstChain => FeeConfig cfg) crosschainFeeConfig;
mapping(address user => FeeConfig cfg) userBurnFeeConfig;
event DefaultFeeConfigSet(Operation indexed _op, FeeConfig _config);
event CrosschainFeeConfigSet(bytes32 indexed _chain, FeeConfig _config);
event UserBurnFeeConfigSet(address indexed _user, FeeConfig _config);
constructor(address _owner) {
initialize(_owner);
}
function initialize(address _owner) public initializer {
__BaseOwnableUpgradeable_init(_owner);
}
function _validateOp(Operation op) internal pure {
require(
op == Operation.Mint ||
op == Operation.Burn ||
op == Operation.CrosschainRequest,
"Invalid op"
);
}
function _getFee(
uint256 _amount,
FeeConfig storage _config
) internal view returns (uint256 _fee) {
uint256 minFee = _config.minFee;
require(minFee < _amount, "amount lower than minimal fee");
uint256 length = _config.tiers.length;
require(length > 0, "Empty fee config");
for (uint i = 0; i < length; i++) {
FeeTier storage tier = _config.tiers[i];
if (i == length - 1 || _amount < uint256(tier.amountTier)) {
// Note: Use `<` instead of `<=`, the border is not included.
_fee = (uint256(tier.feeRate) * _amount) / FEE_RATE_BASE;
break;
}
}
// Cap the fee.
if (_fee < minFee) {
_fee = minFee;
}
uint256 maxFee = _config.maxFee;
if (_fee > maxFee) {
_fee = maxFee;
}
}
function _validateConfig(FeeConfig calldata _config) internal pure {
uint224 prevAmount = 0;
require(
_config.minFee <= 0.03 * 1e8,
"Minimium fee should not exceed 0.03 FBTC"
);
require(
_config.minFee <= _config.maxFee,
"Minimium fee should not exceed maximum fee"
);
require(_config.tiers.length > 0, "Empty fee config");
for (uint i = 0; i < _config.tiers.length; i++) {
FeeTier calldata tier = _config.tiers[i];
uint224 amount = tier.amountTier;
require(amount >= prevAmount, "Tiers not in order");
prevAmount = amount;
require(
tier.feeRate <= FEE_RATE_BASE / 100,
"Fee rate too high, > 1%"
);
if (i == _config.tiers.length - 1) {
require(
amount == type(uint224).max,
"The last tier should be uint224.max"
);
}
}
}
function setDefaultFeeConfig(
Operation _op,
FeeConfig calldata _config
) external onlyOwner {
_validateOp(_op);
_validateConfig(_config);
defaultFeeConfig[_op] = _config;
emit DefaultFeeConfigSet(_op, _config);
}
function setCrosschainFeeConfig(
bytes32 _dstChain,
FeeConfig calldata _config
) external onlyOwner {
_validateConfig(_config);
crosschainFeeConfig[_dstChain] = _config;
emit CrosschainFeeConfigSet(_dstChain, _config);
}
function setUserBurnFeeConfig(
address _user,
FeeConfig calldata _config
) external onlyOwner {
_validateConfig(_config);
userBurnFeeConfig[_user] = _config;
emit UserBurnFeeConfigSet(_user, _config);
}
// View functions.
function getFee(Request calldata r) external view returns (uint256 _fee) {
_validateOp(r.op);
FeeConfig storage _config;
if (r.op == Operation.CrosschainRequest) {
_config = crosschainFeeConfig[r.dstChain];
if (_config.tiers.length > 0) return _getFee(r.amount, _config);
} else if (r.op == Operation.Burn) {
address _user = abi.decode(r.srcAddress, (address));
_config = userBurnFeeConfig[_user];
if (_config.tiers.length > 0) return _getFee(r.amount, _config);
}
_config = defaultFeeConfig[r.op];
if (_config.tiers.length > 0) return _getFee(r.amount, _config);
revert("Fee config not set");
}
function getDefaultFeeConfig(
Operation _op
) external view returns (FeeConfig memory _config) {
_config = defaultFeeConfig[_op];
require(_config.tiers.length > 0, "Fee config not set");
}
function getCrosschainFeeConfig(
bytes32 _dstChain
) external view returns (FeeConfig memory _config) {
_config = crosschainFeeConfig[_dstChain];
require(_config.tiers.length > 0, "Fee config not set");
}
function getUserBurnFeeConfig(
address _user
) external view returns (FeeConfig memory _config) {
_config = userBurnFeeConfig[_user];
require(_config.tiers.length > 0, "Fee config not set");
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {Operation, ChainCode} from "./Common.sol";
import {FBTC} from "./FBTC.sol";
import {FireBridge} from "./FireBridge.sol";
import {FeeModel} from "./FeeModel.sol";
import {BaseSafeModule} from "./base/BaseSafeModule.sol";
contract FBTCGovernorModule is BaseSafeModule {
bytes32 public constant FBTC_PAUSER_ROLE = "1_fbtc_pauser";
bytes32 public constant LOCKER_ROLE = "2_fbtc_locker";
bytes32 public constant BRIDGE_PAUSER_ROLE = "3_bridge_pauser";
bytes32 public constant USER_MANAGER_ROLE = "4_bridge_user_manager";
bytes32 public constant CHAIN_MANAGER_ROLE = "5_bridge_chain_manager";
bytes32 public constant FEE_UPDATER_ROLE = "6_bridge_fee_updater";
FBTC public fbtc;
uint256 public maxCrossChainMinFee;
uint256 public maxUserBurnMinFee;
uint256 public maxUserBurnFeeRate;
event FBTCSet(address indexed _fbtc);
event MaxCrossChainMinFeeSet(uint256 indexed _maxCrossChainMinFee);
event MaxUserBurnMinFeeSet(uint256 indexed _maxUserBurnMinFee);
event MaxUserBurnFeeRateSet(uint256 indexed _maxUserBurnFeeRate);
constructor(address _owner, address _fbtc) {
initialize(_owner, _fbtc);
}
function initialize(address _owner, address _fbtc) public initializer {
__BaseOwnableUpgradeable_init(_owner);
fbtc = FBTC(_fbtc);
maxCrossChainMinFee = 0.03 * 1e8; // 0.03 FBTC
maxUserBurnMinFee = 0.03 * 1e8; // 0.03 FBTC
maxUserBurnFeeRate = 2 * 1_000_000 / 1000; // 0.2%
}
function bridge() public view returns (FireBridge _bridge) {
_bridge = FireBridge(fbtc.bridge());
}
function feeModel() public view returns (FeeModel _feeModel) {
_feeModel = FeeModel(bridge().feeModel());
}
// Admin methods.
function setFBTC(address _fbtc) external onlyOwner {
fbtc = FBTC(_fbtc);
emit FBTCSet(_fbtc);
}
function setMaxCrossChainMinFee(uint256 _maxCrossChainMinFee) external onlyOwner {
maxCrossChainMinFee = _maxCrossChainMinFee;
emit MaxCrossChainMinFeeSet(_maxCrossChainMinFee);
}
function setMaxUserBurnMinFee(uint256 _maxUserBurnMinFee) external onlyOwner {
maxUserBurnMinFee = _maxUserBurnMinFee;
emit MaxUserBurnMinFeeSet(_maxUserBurnMinFee);
}
function setMaxUserBurnFeeRate(uint256 _maxUserBurnFeeRate) external onlyOwner {
maxUserBurnFeeRate = _maxUserBurnFeeRate;
emit MaxUserBurnFeeRateSet(_maxUserBurnFeeRate);
}
// Operator methods.
function lockUserFBTCTransfer(
address _user
) external onlyRole(LOCKER_ROLE) {
_call(address(fbtc), abi.encodeCall(fbtc.lockUser, (_user)));
}
function pauseFBTC() external onlyRole(FBTC_PAUSER_ROLE) {
_call(address(fbtc), abi.encodeCall(fbtc.pause, ()));
}
function pauseBridge() external onlyRole(BRIDGE_PAUSER_ROLE) {
FireBridge _bridge = bridge();
_call(address(_bridge), abi.encodeCall(_bridge.pause, ()));
}
function addQualifiedUser(
address _qualifiedUser,
string calldata _depositAddress,
string calldata _withdrawalAddress
) external onlyRole(USER_MANAGER_ROLE) {
FireBridge _bridge = bridge();
_call(
address(_bridge),
abi.encodeCall(
_bridge.addQualifiedUser,
(_qualifiedUser, _depositAddress, _withdrawalAddress)
)
);
}
function lockQualifiedUser(
address _qualifiedUser
) external onlyRole(USER_MANAGER_ROLE) {
FireBridge _bridge = bridge();
_call(
address(_bridge),
abi.encodeCall(_bridge.lockQualifiedUser, (_qualifiedUser))
);
}
function addDstChains(
bytes32[] memory _dstChains
) external onlyRole(CHAIN_MANAGER_ROLE) {
FireBridge _bridge = bridge();
_call(
address(_bridge),
abi.encodeCall(_bridge.addDstChains, (_dstChains))
);
}
function removeDstChains(
bytes32[] memory _dstChains
) external onlyRole(CHAIN_MANAGER_ROLE) {
FireBridge _bridge = bridge();
_call(
address(_bridge),
abi.encodeCall(_bridge.removeDstChains, (_dstChains))
);
}
function updateCrossChainMinFee(
bytes32 _chain,
uint256 _minFee
) external onlyRole(FEE_UPDATER_ROLE) {
require(
_minFee <= maxCrossChainMinFee,
"Min fee exceeds maxCrossChainMinFee"
);
// Copy the config set by admin.
FeeModel _feeModel = feeModel();
FeeModel.FeeConfig memory config;
try _feeModel.getCrosschainFeeConfig(
_chain
) returns (FeeModel.FeeConfig memory _config){
config = _config;
} catch {
// If no CrosschainFeeConfig is set, get the default one.
config = _feeModel.getDefaultFeeConfig(Operation.CrosschainRequest);
}
// Update minFee.
config.minFee = _minFee;
_call(
address(_feeModel),
abi.encodeCall(_feeModel.setCrosschainFeeConfig, (_chain, config))
);
}
function updateUserBurnFee(
address _user,
FeeModel.FeeConfig calldata _config
) external onlyRole(USER_MANAGER_ROLE) {
require(_config.minFee <= maxUserBurnMinFee, "Min fee exceeds maxUserBurnMinFee");
for (uint i = 0; i < _config.tiers.length; i++) {
FeeModel.FeeTier calldata tier = _config.tiers[i];
require(
tier.feeRate <= maxUserBurnFeeRate,
"Fee rate exceeds maxUserBurnFeeRate"
);
}
FeeModel _feeModel = feeModel();
_call(
address(_feeModel),
abi.encodeCall(_feeModel.setUserBurnFeeConfig, (_user, _config))
);
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {BasePausableUpgradeable} from "./BasePausableUpgradeable.sol";
abstract contract FToken is ERC20Upgradeable, BasePausableUpgradeable {
address public bridge;
mapping(address user => bool blocked) public userBlocked;
event UserLocked(address indexed user);
event UserUnlocked(address indexed user);
event BridgeUpdated(address indexed newBridge, address indexed oldBridge);
modifier onlyBridge() {
require(msg.sender == bridge, "Caller not bridge");
_;
}
function __FToken_init(
address _owner,
address _bridge,
string memory _name,
string memory _symbol
) internal onlyInitializing {
__ERC20_init(_name, _symbol);
__BasePausableUpgradeable_init(_owner);
bridge = _bridge;
}
function lockUser(address _user) external onlyOwner {
userBlocked[_user] = true;
emit UserLocked(_user);
}
function unlockUser(address _user) external onlyOwner {
userBlocked[_user] = false;
emit UserUnlocked(_user);
}
function setBridge(address _bridge) external onlyOwner {
address oldBridge = bridge;
bridge = _bridge;
emit BridgeUpdated(_bridge, oldBridge);
}
function mint(address to, uint256 amount) external onlyBridge {
_mint(to, amount);
}
function burn(address from, uint256 amount) external onlyBridge {
_burn(from, amount);
}
function payFee(
address payer,
address feeRecipient,
uint256 amount
) external onlyBridge {
_transfer(payer, feeRecipient, amount);
}
function _update(
address from,
address to,
uint256 value
) internal override whenNotPaused {
require(!userBlocked[from], "from is blocked");
require(!userBlocked[to], "to is blocked");
super._update(from, to, value);
}
uint256[50] private __gap;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Request, UserInfo} from "../Common.sol";
contract BridgeStorage {
address public minter;
address public fbtc;
EnumerableSet.AddressSet internal qualifiedUsers;
EnumerableSet.Bytes32Set internal dstChains;
mapping(address qualifiedUser => UserInfo info) public userInfo;
mapping(string depositAddress => address qualifiedUser)
public depositAddressToUser; // For uniqueness check
bytes32[] public requestHashes;
mapping(bytes32 _hash => Request r) public requests;
mapping(bytes32 bytesHash => bytes32 requestHash) public usedDepositTxs;
mapping(bytes32 bytesHash => bytes32 requestHash) public usedWithdrawalTxs;
mapping(bytes32 srcHash => bytes32 dstHash)
public crosschainRequestConfirmation;
address public feeModel;
address public feeRecipient;
uint256[50] private __gap;
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {BaseOwnableUpgradeable} from "./BaseOwnableUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
abstract contract BasePausableUpgradeable is
BaseOwnableUpgradeable,
PausableUpgradeable
{
function __BasePausableUpgradeable_init(
address initialOwner
) internal onlyInitializing {
__BaseOwnableUpgradeable_init(initialOwner);
__Pausable_init();
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {BaseOwnableUpgradeable} from "./BaseOwnableUpgradeable.sol";
/// @dev A simple RoleBasedAccessControl module modified from
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/extensions/AccessControlEnumerable.sol
abstract contract RoleBasedAccessControl is BaseOwnableUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 role => EnumerableSet.AddressSet) private _roleMembers;
event RoleGranted(bytes32 indexed role, address indexed account);
event RoleRevoked(bytes32 indexed role, address indexed account);
modifier onlyRole(bytes32 role) {
require(hasRole(role, msg.sender), "Unauthorized role member");
_;
}
function _grantRole(bytes32 role, address account) internal {
if (_roleMembers[role].add(account)) {
emit RoleGranted(role, account);
}
}
function _revokeRole(bytes32 role, address account) internal {
if (_roleMembers[role].remove(account)) {
emit RoleRevoked(role, account);
}
}
function grantRole(bytes32 role, address account) external onlyOwner {
_grantRole(role, account);
}
function revokeRole(bytes32 role, address account) external onlyOwner {
_revokeRole(role, account);
}
function hasRole(bytes32 role, address account) public view returns (bool) {
return _roleMembers[role].contains(account);
}
function getRoleMember(
bytes32 role,
uint256 index
) external view returns (address) {
return _roleMembers[role].at(index);
}
function getRoleMemberCount(bytes32 role) external view returns (uint256) {
return _roleMembers[role].length();
}
function getRoleMembers(
bytes32 role
) external view returns (address[] memory) {
return _roleMembers[role].values();
}
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
abstract contract BaseOwnableUpgradeable is
Ownable2StepUpgradeable,
UUPSUpgradeable
{
using SafeERC20 for IERC20;
function __BaseOwnableUpgradeable_init(
address initialOwner
) internal onlyInitializing {
__Ownable_init(initialOwner);
__Ownable2Step_init();
__UUPSUpgradeable_init();
}
function renounceOwnership() public override {
revert("Unable to renounce ownership");
}
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
/// @notice Rescue and transfer assets locked in this contract.
function rescue(address token, address to) external onlyOwner {
if (token == address(0)) {
(bool success, ) = payable(to).call{value: address(this).balance}(
""
);
require(success, "ETH transfer failed");
} else {
IERC20(token).safeTransfer(
to,
IERC20(token).balanceOf(address(this))
);
}
}
uint256[50] private __gap;
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.20;
import {RoleBasedAccessControl} from "./RoleBasedAccessControl.sol";
contract Enum {
enum Operation {
Call,
DelegateCall
}
}
interface ISafe {
/// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data
function execTransactionFromModuleReturnData(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation
) external returns (bool success, bytes memory returnData);
}
abstract contract BaseSafeModule is RoleBasedAccessControl {
function _call(address _to, bytes memory _data) internal {
(bool success, bytes memory _retData) = ISafe(safe())
.execTransactionFromModuleReturnData(
_to,
0,
_data,
Enum.Operation.Call
);
if (!success) {
assembly {
let size := mload(_retData)
revert(add(32, _retData), size)
}
}
}
function safe() public view returns (address) {
return owner();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {Initializable} from "../../proxy/utils/Initializable.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 ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors {
/// @custom:storage-location erc7201:openzeppelin.storage.ERC20
struct ERC20Storage {
mapping(address account => uint256) _balances;
mapping(address account => mapping(address spender => uint256)) _allowances;
uint256 _totalSupply;
string _name;
string _symbol;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00;
function _getERC20Storage() private pure returns (ERC20Storage storage $) {
assembly {
$.slot := ERC20StorageLocation
}
}
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
__ERC20_init_unchained(name_, symbol_);
}
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
ERC20Storage storage $ = _getERC20Storage();
$._name = name_;
$._symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
ERC20Storage storage $ = _getERC20Storage();
return $._name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
ERC20Storage storage $ = _getERC20Storage();
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;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
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;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
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}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* 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 {
ERC20Storage storage $ = _getERC20Storage();
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:
* ```
* 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 {
ERC20Storage storage $ = _getERC20Storage();
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);
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
return $._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
struct Ownable2StepStorage {
address _pendingOwner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;
function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
assembly {
$.slot := Ownable2StepStorageLocation
}
}
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
function __Ownable2Step_init() internal onlyInitializing {
}
function __Ownable2Step_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
Ownable2StepStorage storage $ = _getOwnable2StepStorage();
return $._pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
Ownable2StepStorage storage $ = _getOwnable2StepStorage();
$._pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
Ownable2StepStorage storage $ = _getOwnable2StepStorage();
delete $._pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.20;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
function __UUPSUpgradeable_init() internal onlyInitializing {
}
function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
}
/**
* @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC1967-compliant implementation pointing to self.
* See {_onlyProxy}.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @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 ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 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.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @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.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
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) {
OwnableStorage storage $ = _getOwnableStorage();
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 {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC1822.sol)
pragma solidity ^0.8.20;
/**
* @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy.
*/
function proxiableUUID() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.20;
import {IBeacon} from "../beacon/IBeacon.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*/
library ERC1967Utils {
// We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
// This will be fixed in Solidity 0.8.21. At that point we should remove these events.
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.20;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* 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;
* }
* }
* ```
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 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) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
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) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
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) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}