Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/IS21Engine.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20Pausable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/// @notice Public struct for fiat reserve metadata
struct FiatReserves {
bytes32 currency; // The currency code (e.g., "USD", "EUR", "ZAR").
uint256 amount; // The amount of fiat in the smallest unit (e.g., cents).
}
/**
* @title IS21Engine
* @author BlueAsset Technology Team
*
* @notice The IS21Engine contract is the foundation of the InterShare21 reserve currency system.
* It governs the issuance and management of InterShare21 (IS21) tokens, a decentralized,
* exogenously collateralized, fiat-backed currency. It is the official token contract for IS21 and
* for the InterShare Loan Engine (ISLoanEngine).
*
* @dev Key Principles:
* - Exogenously Collateralized: Backed by external fiat reserves stored in trusted institutions.
* - Fiat Backed: Each IS21 token corresponds to reserves of major global fiat currencies.
* - Managed Supply: IS21 tokens are minted or burned in response to verified reserve changes.
* - Transparent Verification: Approved auditors publish proof-of-reserve reports (e.g., via IPFS/Arweave),
* and fund managers ensure reserves remain adequate.
*
* @dev Contract Capabilities:
* - Minting and burning of IS21 tokens by approved fund managers.
* - Approval and revocation of auditors and fund managers.
* - Recording and publishing of off-chain reserve proof hashes.
* - Fiat reserve tracking across multiple currencies.
* - Pausable contract functionality for added security.
*
* @notice IS21 is designed as a global reserve currency system to provide stability,
* transparency, and decentralization in financial ecosystems.
*/
contract IS21Engine is
ERC20,
ERC20Permit,
ReentrancyGuard,
ERC20Pausable,
Ownable
{
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;
///////////////
// Errors //
///////////////
error IS21Engine__AmountMustBeMoreThanZero();
error IS21Engine__BurnAmountExceedsBalance();
error IS21Engine__NotZeroAddress();
error IS21Engine__OnlyFundManagerCanExecute();
error IS21Engine__OnlyAuditorCanExecute();
error IS21Engine__ETHNotAccepted();
error IS21Engine__CannotSendToContract();
/////////////////////
// State Variables //
/////////////////////
string public constant IS21_VERSION = "1.0.0"; // Semantic versioning for the IS21Engine contract
mapping(bytes32 => uint256) private sCurrencyToReserve;
EnumerableSet.AddressSet private sFundManagers;
EnumerableSet.AddressSet private sAuditors;
string private sLatestReserveProofHash;
////////////
// Events //
////////////
event IS21Minted(address indexed to, uint256 amount);
event IS21Burned(address indexed from, uint256 amount);
event AuditorApproved(address indexed auditor, uint256 timestamp);
event AuditorRevoked(address indexed auditor, uint256 timestamp);
event FundManagerApproved(address indexed fundManager, uint256 timestamp);
event FundManagerRevoked(address indexed fundManager, uint256 timestamp);
event ContractPaused(address indexed caller, uint256 timestamp);
event ContractUnpaused(address indexed caller, uint256 timestamp);
event ReserveVerified(
address indexed auditor,
uint256 timestamp,
string message
);
event FiatReserveUpdated(
address indexed fundManager,
bytes32 indexed currency,
uint256 amount,
uint256 timestamp
);
event ReserveProofHashUpdated(
address indexed auditor,
string oldHash,
string newHash,
uint256 timestamp
);
///////////////
// Modifiers //
///////////////
modifier moreThanZero(uint256 amount) {
if (amount <= 0) {
revert IS21Engine__AmountMustBeMoreThanZero();
}
_;
}
modifier nonZeroAddress(address to) {
if (to == address(0)) {
revert IS21Engine__NotZeroAddress();
}
_;
}
modifier onlyFundManager() {
if (!sFundManagers.contains(msg.sender)) {
revert IS21Engine__OnlyFundManagerCanExecute();
}
_;
}
modifier onlyAuditor() {
if (!sAuditors.contains(msg.sender)) {
revert IS21Engine__OnlyAuditorCanExecute();
}
_;
}
modifier cannotSendToContract(address to) {
if (to == address(this)) {
revert IS21Engine__CannotSendToContract();
}
_;
}
/////////////////
// Constructor //
/////////////////
/**
@param ownerAddress The address of the owner of the contract.
@notice The owner address cannot be zero.
@notice The contract is initialized with the name "InterShare21" and symbol "IS21".
@dev The contract uses OpenZeppelin's ERC20 implementation for token functionality.
@dev The constructor initializes the contract with the owner's address.
*/
constructor(
address ownerAddress
)
ERC20("InterShare21", "IS21")
ERC20Permit("InterShare21")
Ownable(ownerAddress)
{
if (ownerAddress == address(0)) {
revert IS21Engine__NotZeroAddress();
}
}
///////////////////////////////////
// External/Public Functions //
///////////////////////////////////
/**
@return The number of decimals used to get its user representation.
@dev This function overrides the default decimals function in the ERC20 contract.
@dev IS21 uses 18 decimals, similar to Ether and many other ERC20 tokens.
*/
function decimals() public pure override returns (uint8) {
return 18;
}
/**
@return The current version of the IS21Engine contract as a string.
@dev This function returns a constant string defined in the contract.
*/
function getVersion() external pure returns (string memory) {
return IS21_VERSION;
}
/**
This function allows the owner to rescue ERC20 tokens from the contract.
@param token The address of the ERC20 token contract.
@param amount The amount of tokens to rescue.
@param to The address to which the rescued tokens will be sent.
@notice This function can only be called by the owner and is protected against reentrancy attacks.
@dev It uses the transfer function from the IERC20 interface to send the tokens.
*/
function rescueErc20(
address token,
uint256 amount,
address to
) external onlyOwner nonZeroAddress(to) nonZeroAddress(token) nonReentrant {
IERC20(token).safeTransfer(to, amount);
}
/**
Returns the latest reserve proof hash published by an auditor.
@return The string hash (e.g., IPFS CID) pointing to the proof document.
*/
function getLatestReserveProofHash() external view returns (string memory) {
return sLatestReserveProofHash;
}
/**
Allows an approved auditor to update the off-chain proof-of-reserve hash.
@param newHash The IPFS or Arweave hash of the signed reserve audit report.
@notice This function can only be called by an approved auditor.
@dev Emits an event to log and trace reserve proof updates.
*/
function setReserveProofHash(
string calldata newHash
) external onlyAuditor nonReentrant {
string memory oldHash = sLatestReserveProofHash;
sLatestReserveProofHash = newHash;
emit ReserveProofHashUpdated(
msg.sender,
oldHash,
newHash,
block.timestamp
);
}
/**
This function allows the owner to approve a fund manager.
@param fundManager The address of the fund manager to be approved.
@notice This function can only be called by the current owner and is protected against reentrancy attacks.
@dev It updates the mapping of approved fund managers and emits an event for tracking purposes.
*/
function approveFundManager(
address fundManager
) external onlyOwner nonZeroAddress(fundManager) nonReentrant {
if (sFundManagers.add(fundManager)) {
emit FundManagerApproved(fundManager, block.timestamp);
}
}
/**
This function allows the owner to revoke a fund manager's approval.
@param fundManager The address of the fund manager to be revoked.
@notice This function can only be called by the current owner and is protected against reentrancy attacks.
@dev It updates the mapping of approved fund managers and emits an event for tracking purposes.
*/
function revokeFundManager(
address fundManager
) external onlyOwner nonZeroAddress(fundManager) nonReentrant {
if (sFundManagers.remove(fundManager)) {
emit FundManagerRevoked(fundManager, block.timestamp);
}
}
/**
This function retrieves the list of all approved fund managers.
@return An array of addresses representing the approved fund managers.
@notice This function is view-only and does not modify the state of the contract.
@dev It can be used to get a list of all fund managers for administrative or informational purposes.
*/
function getFundManagers() external view returns (address[] memory) {
return sFundManagers.values();
}
/**
This function checks if an address is an approved fund manager.
@param account The address to check for fund manager approval.
@return A boolean indicating whether the address is an approved fund manager.
@notice This function is view-only and does not modify the state of the contract.
@dev It can be used to verify if an address has fund management privileges.
*/
function isFundManager(address account) external view returns (bool) {
return sFundManagers.contains(account);
}
/**
This function allows the owner to approve an auditor.
@param auditor The address of the auditor to be approved.
@notice This function can only be called by the owner and is protected against reentrancy attacks.
@dev It updates the mapping of approved auditors and emits an event for tracking purposes.
*/
function approveAuditor(
address auditor
) external onlyOwner nonZeroAddress(auditor) nonReentrant {
if (sAuditors.add(auditor)) {
emit AuditorApproved(auditor, block.timestamp);
}
}
/**
This function allows the owner to revoke an auditor's approval.
@param auditor The address of the auditor to be revoked.
@notice This function can only be called by the owner and is protected against reentrancy attacks.
@dev It updates the mapping of approved auditors and emits an event for tracking purposes.
*/
function revokeAuditor(
address auditor
) external onlyOwner nonZeroAddress(auditor) nonReentrant {
if (sAuditors.remove(auditor)) {
emit AuditorRevoked(auditor, block.timestamp);
}
}
/**
This function retrieves the list of all approved auditors.
@return An array of addresses representing the approved auditors.
@notice This function is view-only and does not modify the state of the contract.
@notice Some may currently be revoked; check isAuditor() to confirm active status.
@dev It can be used to get a list of all auditors for administrative or informational purposes.
*/
function getAuditors() external view returns (address[] memory) {
return sAuditors.values();
}
/**
This function checks if an address is an approved auditor.
@param account The address to check for auditor approval.
@return A boolean indicating whether the address is an approved auditor.
@notice This function is view-only and does not modify the state of the contract.
@dev It can be used to verify if an address has auditor privileges.
*/
function isAuditor(address account) external view returns (bool) {
return sAuditors.contains(account);
}
/**
This function allows an auditor to verify the reserves of the contract.
@param message A string message that can be used to provide context or details about the verification.
@notice This function can only be called by an approved auditor and is protected against reentrancy attacks.
@dev It emits a ReserveVerified event with the auditor's address, current timestamp, and the provided message.
@dev The auditor's address must be approved by the owner before calling this function.
*/
function verifyReserves(
string calldata message
) external onlyAuditor nonReentrant {
emit ReserveVerified(msg.sender, block.timestamp, message);
}
/**
This function allows the fund manager to update a single fiat reserve for a specific currency.
@param currency The currency code (e.g., "USD", "EUR", "ZAR") for which the reserve is being updated.
@param amount The amount of fiat in the smallest unit (e.g., cents) to be set as the reserve for the specified currency.
@notice This function can only be called by the fund manager and is protected against reentrancy attacks.
@dev It updates the mapping of currency to reserve and emits an event for tracking purposes.
*/
function updateFiatReserve(
bytes32 currency,
uint256 amount
) external onlyFundManager nonReentrant {
sCurrencyToReserve[currency] = amount;
emit FiatReserveUpdated(msg.sender, currency, amount, block.timestamp);
}
/** This function allows the fund manager to update multiple fiat reserves at once.
@param reserves An array of FiatReserves structs, each containing a currency code and the corresponding amount to be set as the reserve.
@notice This function can only be called by the fund manager and is protected against reentrancy attacks.
@dev It iterates through the array of reserves, updating the mapping of currency to reserve for each entry and emitting an event for each update.
*/
function updateFiatReserves(
FiatReserves[] calldata reserves
) external onlyFundManager nonReentrant {
for (uint256 i = 0; i < reserves.length; ) {
sCurrencyToReserve[reserves[i].currency] = reserves[i].amount;
emit FiatReserveUpdated(
msg.sender,
reserves[i].currency,
reserves[i].amount,
block.timestamp
);
unchecked {
++i;
}
}
}
/**
This function allows anyone to retrieve the fiat reserve for a specific currency.
@param currency The currency code (e.g., "USD", "EUR", "ZAR") for which the reserve is being queried.
@return The amount of fiat in the smallest unit (e.g., cents) that is reserved for the specified currency.
@notice This function is view-only and does not modify the state of the contract.
*/
function getFiatReserve(bytes32 currency) external view returns (uint256) {
return sCurrencyToReserve[currency];
}
/**
This function allows anyone to retrieve the fiat reserves for multiple currencies.
@param currencies An array of currency codes (e.g., ["USD", "EUR", "ZAR"]) for which the reserves are being queried.
@return An array of amounts, where each amount corresponds to the reserve for the specified currency in the same index.
@notice This function is view-only and does not modify the state of the contract.
*/
function getFiatReserves(
bytes32[] calldata currencies
) external view returns (uint256[] memory) {
uint256[] memory reserves = new uint256[](currencies.length);
for (uint256 i = 0; i < currencies.length; ) {
reserves[i] = sCurrencyToReserve[currencies[i]];
unchecked {
++i;
}
}
return reserves;
}
/**
This function allows fund managers to mint IS21 tokens directly to their own address.
@param amount The amount of IS21 tokens to mint.
@notice This function can only be called by an approved fund manager, when not paused, and with a positive amount.
@dev Equivalent to mintIs21To(msg.sender, amount).
*/
function mintIs21(uint256 amount) external {
mintIs21To(msg.sender, amount);
}
/**
@notice Allows an approved fund manager to mint IS21 tokens directly to a specified address.
@param to The address that will receive the minted tokens.
@param amount The amount of IS21 tokens to mint.
@dev Only callable by an approved fund manager, when not paused, and with a positive amount.
**/
function mintIs21To(
address to,
uint256 amount
)
public
onlyFundManager
nonZeroAddress(to)
moreThanZero(amount)
nonReentrant
{
_mintInterShare21(to, amount);
emit IS21Minted(to, amount);
}
/**
This function allows fund managers to burn IS21 tokens.
@param amount The amount of IS21 tokens to burn.
@notice This function can only be called by the fund manager and must be more than zero.
@dev It checks if the sender has enough balance before burning the tokens.
@dev It uses the _burnInterShare21 function to burn the tokens and emits an InterShare21Burned event to log the burning action.
@dev It is protected against reentrancy attacks and can only be executed when the contract is not paused.
*/
function burnIs21(
uint256 amount
) external onlyFundManager moreThanZero(amount) nonReentrant {
if (balanceOf(msg.sender) < amount) {
revert IS21Engine__BurnAmountExceedsBalance();
}
_burnInterShare21(msg.sender, amount);
emit IS21Burned(msg.sender, amount);
}
/**
This function allows fund managers to burn IS21 tokens from a specified account.
@param account The address from which the IS21 tokens will be burned.
@param amount The amount of IS21 tokens to burn.
@notice This function can only be called by the fund manager and must be more than zero.
@dev It checks if the account has enough balance before burning the tokens.
@dev It uses the _spendAllowance function to check and reduce the allowance, and the _burn function to burn the tokens.
@dev It emits an InterShare21Burned event to log the burning action.
@dev It is protected against reentrancy attacks and can only be executed when the contract is not paused.
*/
function burnIs21From(
address account,
uint256 amount
)
external
onlyFundManager
moreThanZero(amount)
nonZeroAddress(account)
nonReentrant
{
_spendAllowance(account, msg.sender, amount);
_burnInterShare21(account, amount);
emit IS21Burned(account, amount);
}
/**
This function allows the owner to pause the contract.
@notice This function can only be called by the owner.
@dev It uses the _pause function from the Pausable contract to pause the contract.
@dev It emits a ContractPaused event to log the pausing action.
*/
function pause() external onlyOwner {
_pause();
emit ContractPaused(msg.sender, block.timestamp);
}
/**
This function allows the owner to unpause the contract.
@notice This function can only be called by the owner.
@dev It uses the _unpause function from the Pausable contract to unpause the contract.
@dev It emits a ContractUnpaused event to log the unpausing action.
*/
function unpause() external onlyOwner {
_unpause();
emit ContractUnpaused(msg.sender, block.timestamp);
}
/**
This function specifies that the contract does not accept ETH.
@notice It reverts any incoming ETH transactions with a custom error.
@dev This function is used to prevent accidental ETH transfers to the contract.
*/
receive() external payable {
revert IS21Engine__ETHNotAccepted();
}
/**
This function specifies that the contract does not accept ETH.
@notice It reverts any incoming ETH transactions with a custom error.
@dev This function is used to prevent accidental ETH transfers to the contract.
*/
fallback() external payable {
revert IS21Engine__ETHNotAccepted();
}
////////////////////////////////////
// Internal/Private Functions //
////////////////////////////////////
/**
This function burns InterShare21 tokens from the sender's balance.
@param amount The amount of InterShare21 tokens to burn.
@notice This function is private and can only be called internally.
@dev It checks if the sender has enough balance before burning the tokens.
*/
function _burnInterShare21(address from, uint256 amount) private {
_burn(from, amount);
}
/**
This function mints InterShare21 tokens to a specified address.
@param to The address to which the InterShare21 tokens will be minted.
@param amount The amount of InterShare21 tokens to mint.
@notice This function is private and can only be called internally.
@dev It uses the _mint function from the ERC20 contract to mint the tokens.
@dev It includes a check to prevent minting tokens to the contract itself.
*/
function _mintInterShare21(
address to,
uint256 amount
) private cannotSendToContract(to) {
_mint(to, amount);
}
/**
This function overrides the _update function from the ERC20 and ERC20Pausable contracts.
It is called during token transfers, mints, and burns to ensure that the contract is not paused.
@param from The address from which tokens are being transferred.
@param to The address to which tokens are being transferred.
@param value The amount of tokens being transferred.
@dev It calls the super implementation of the _update function to perform the actual token transfer logic.
@dev It adds a check to prevent transfers to the token contract itself.
*/
function _update(
address from,
address to,
uint256 value
) internal override(ERC20, ERC20Pausable) cannotSendToContract(to) {
super._update(from, to, value);
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner`'s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
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);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Pausable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Pausable.sol)
pragma solidity ^0.8.20;
import {ERC20} from "../ERC20.sol";
import {Pausable} from "../../../utils/Pausable.sol";
/**
* @dev ERC-20 token with pausable token transfers, minting and burning.
*
* Useful for scenarios such as preventing trades until the end of an evaluation
* period, or having an emergency switch for freezing all token transfers in the
* event of a large bug.
*
* IMPORTANT: This contract does not include public pause and unpause functions. In
* addition to inheriting this contract, you must define both functions, invoking the
* {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate
* access control, e.g. using {AccessControl} or {Ownable}. Not doing so will
* make the contract pause mechanism of the contract unreachable, and thus unusable.
*/
abstract contract ERC20Pausable is ERC20, Pausable {
/**
* @dev See {ERC20-_update}.
*
* Requirements:
*
* - the contract must not be paused.
*/
function _update(address from, address to, uint256 value) internal virtual override whenNotPaused {
super._update(from, to, value);
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Permit.sol)
pragma solidity ^0.8.20;
import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";
/**
* @dev Implementation of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
bytes32 private constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev Permit deadline has expired.
*/
error ERC2612ExpiredSignature(uint256 deadline);
/**
* @dev Mismatched signature.
*/
error ERC2612InvalidSigner(address signer, address owner);
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC-20 token name.
*/
constructor(string memory name) EIP712(name, "1") {}
/**
* @inheritdoc IERC20Permit
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}
bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}
_approve(owner, spender, value);
}
/**
* @inheritdoc IERC20Permit
*/
function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
/**
* @inheritdoc IERC20Permit
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
return _domainSeparatorV4();
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 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 {
/**
* @dev An operation with an ERC-20 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 Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(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.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
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.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
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.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
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 Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
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 silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
import {Arrays} from "../Arrays.sol";
/**
* @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.
* - Set can be cleared (all elements removed) in O(n).
*
* ```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 Removes all the values from a set. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
*/
function _clear(Set storage set) private {
uint256 len = _length(set);
for (uint256 i = 0; i < len; ++i) {
delete set._positions[set._values[i]];
}
Arrays.unsafeSetLength(set._values, 0);
}
/**
* @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 v
Submitted on: 2025-10-30 14:25:50
Comments
Log in to comment.
No comments yet.