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/modules/AlephVaultSettlement.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
/*
______ __ __
/ \ / | / |
/$$$$$$ |$$ | ______ ______ $$ |____
$$ |__$$ |$$ | / \ / \ $$ \
$$ $$ |$$ |/$$$$$$ |/$$$$$$ |$$$$$$$ |
$$$$$$$$ |$$ |$$ $$ |$$ | $$ |$$ | $$ |
$$ | $$ |$$ |$$$$$$$$/ $$ |__$$ |$$ | $$ |
$$ | $$ |$$ |$$ |$$ $$/ $$ | $$ |
$$/ $$/ $$/ $$$$$$$/ $$$$$$$/ $$/ $$/
$$ |
$$ |
$$/
*/
import {EnumerableSet} from "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {IAlephVault} from "@aleph-vault/interfaces/IAlephVault.sol";
import {IAlephVaultSettlement} from "@aleph-vault/interfaces/IAlephVaultSettlement.sol";
import {IFeeManager} from "@aleph-vault/interfaces/IFeeManager.sol";
import {AuthLibrary} from "@aleph-vault/libraries/AuthLibrary.sol";
import {ERC4626Math} from "@aleph-vault/libraries/ERC4626Math.sol";
import {ModulesLibrary} from "@aleph-vault/libraries/ModulesLibrary.sol";
import {SeriesAccounting} from "@aleph-vault/libraries/SeriesAccounting.sol";
import {AlephVaultBase} from "@aleph-vault/AlephVaultBase.sol";
import {AlephVaultStorageData} from "@aleph-vault/AlephVaultStorage.sol";
/**
* @author Othentic Labs LTD.
* @notice Terms of Service: https://aleph.finance/terms-of-service
*/
contract AlephVaultSettlement is IAlephVaultSettlement, AlephVaultBase {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;
using SeriesAccounting for IAlephVault.ShareClass;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Constructor for AlephVaultSettlement module
* @param _batchDuration The duration of each batch cycle in seconds
*/
constructor(uint48 _batchDuration) AlephVaultBase(_batchDuration) {}
/*//////////////////////////////////////////////////////////////
SETTLEMENT FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IAlephVaultSettlement
function settleDeposit(SettlementParams calldata _settlementParams) external nonReentrant {
_settleDeposit(_getStorage(), _settlementParams);
}
/// @inheritdoc IAlephVaultSettlement
function settleRedeem(SettlementParams calldata _settlementParams) external nonReentrant {
_settleRedeem(_getStorage(), _settlementParams);
}
/// @inheritdoc IAlephVaultSettlement
function forceRedeem(address _user) external nonReentrant {
_forceRedeem(_getStorage(), _user);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @dev Internal function to settle all deposits for batches up to the current batch.
* @param _sd The storage struct.
* @param _settlementParams The parameters for the settlement.
*/
function _settleDeposit(AlephVaultStorageData storage _sd, SettlementParams calldata _settlementParams) internal {
// verify all conditions are satisfied to settle deposits
if (_settlementParams.toBatchId > _currentBatch(_sd)) {
revert InvalidToBatchId();
}
IAlephVault.ShareClass storage _shareClass = _sd.shareClasses[_settlementParams.classId];
uint48 _depositSettleId = _shareClass.depositSettleId;
if (_settlementParams.toBatchId <= _depositSettleId) {
revert NoDepositsToSettle();
}
uint32 _lastConsolidatedSeriesId = _shareClass.lastConsolidatedSeriesId;
_validateNewTotalAssets(_shareClass.shareSeriesId, _lastConsolidatedSeriesId, _settlementParams.newTotalAssets);
if (_sd.isSettlementAuthEnabled) {
AuthLibrary.verifySettlementAuthSignature(
AuthLibrary.SETTLE_DEPOSIT,
_settlementParams.classId,
_settlementParams.toBatchId,
_sd.manager,
_settlementParams.newTotalAssets,
_sd.authSigner,
_settlementParams.authSignature
);
}
// accumalate fees if applicable
_accumulateFees(
_shareClass,
_settlementParams.classId,
_lastConsolidatedSeriesId,
_settlementParams.toBatchId,
_settlementParams.newTotalAssets
);
uint32 _settlementSeriesId = _handleSeriesAccounting(
_shareClass, _settlementParams.classId, _lastConsolidatedSeriesId, _settlementParams.toBatchId
);
IAlephVault.ShareSeries storage _shareSeries = _shareClass.shareSeries[_settlementSeriesId];
SettleDepositDetails memory _settleDepositDetails = SettleDepositDetails({
// check if a new series needs to be created
createSeries: _settlementSeriesId > 0,
classId: _settlementParams.classId,
seriesId: _settlementSeriesId,
batchId: _depositSettleId,
toBatchId: _settlementParams.toBatchId,
totalAssets: _shareSeries.totalAssets,
totalShares: _shareSeries.totalShares
});
uint256 _amountToSettle;
for (
_settleDepositDetails.batchId;
_settleDepositDetails.batchId < _settlementParams.toBatchId;
_settleDepositDetails.batchId++
) {
// settle deposits for each unsettled batch
(uint256 _amount, uint256 _sharesToMint) = _settleDepositForBatch(_shareClass, _settleDepositDetails);
_amountToSettle += _amount;
_settleDepositDetails.totalAssets += _amount;
_settleDepositDetails.totalShares += _sharesToMint;
}
_shareClass.depositSettleId = _settlementParams.toBatchId;
_shareSeries.totalAssets = _settleDepositDetails.totalAssets;
_shareSeries.totalShares = _settleDepositDetails.totalShares;
_sd.totalAmountToDeposit -= _amountToSettle;
uint256 _requiredVaultBalance = _amountToSettle + _sd.totalAmountToDeposit + _sd.totalAmountToWithdraw;
if (IERC20(_sd.underlyingToken).balanceOf(address(this)) < _requiredVaultBalance) {
revert InsufficientAssetsToSettle(_requiredVaultBalance);
}
if (_amountToSettle > 0) {
IERC20(_sd.underlyingToken).safeTransfer(_sd.custodian, _amountToSettle);
}
emit SettleDeposit(
_settlementParams.classId,
_settleDepositDetails.seriesId,
_depositSettleId,
_settlementParams.toBatchId,
_amountToSettle,
_settleDepositDetails.totalAssets,
_settleDepositDetails.totalShares
);
}
/**
* @dev Internal function to settle deposits for a specific batch.
* @param _shareClass The share class to settle.
* @param _settleDepositDetails The parameters for the settlement.
* @return The total amount settled for the batch.
* @return The total shares minted for the batch.
*/
function _settleDepositForBatch(
IAlephVault.ShareClass storage _shareClass,
SettleDepositDetails memory _settleDepositDetails
) internal returns (uint256, uint256) {
IAlephVault.DepositRequests storage _depositRequests =
_shareClass.depositRequests[_settleDepositDetails.batchId];
uint256 _totalAmountToDeposit = _depositRequests.totalAmountToDeposit;
// if there are no deposits to settle, return 0
if (_totalAmountToDeposit == 0) {
return (0, 0);
}
// create a new series only if there are deposits to settle (and createSeries flag is true)
if (_settleDepositDetails.createSeries) {
_shareClass.createNewSeries(_settleDepositDetails.classId, _settleDepositDetails.toBatchId);
_settleDepositDetails.createSeries = false;
}
uint256 _totalSharesToMint;
uint256 _len = _depositRequests.usersToDeposit.length();
// iterate through all requests in batch (one user can only make one request per batch)
for (uint256 _i; _i < _len; _i++) {
DepositRequestDetails memory _depositRequestDetails;
_depositRequestDetails.user = _depositRequests.usersToDeposit.at(_i);
_depositRequestDetails.amount = _depositRequests.depositRequest[_depositRequestDetails.user];
_depositRequestDetails.sharesToMint = ERC4626Math.previewDeposit(
_depositRequestDetails.amount, _settleDepositDetails.totalShares, _settleDepositDetails.totalAssets
);
_totalSharesToMint += _depositRequestDetails.sharesToMint;
_shareClass.shareSeries[_settleDepositDetails.seriesId].sharesOf[_depositRequestDetails.user] +=
_depositRequestDetails.sharesToMint;
// add user into settlement series if they don't already exist there
if (!_shareClass.shareSeries[_settleDepositDetails.seriesId].users.contains(_depositRequestDetails.user)) {
_shareClass.shareSeries[_settleDepositDetails.seriesId].users.add(_depositRequestDetails.user);
}
// delete user deposit request
delete _depositRequests.depositRequest[_depositRequestDetails.user];
emit IAlephVaultSettlement.DepositRequestSettled(
_settleDepositDetails.classId,
_settleDepositDetails.seriesId,
_settleDepositDetails.batchId,
_depositRequestDetails.user,
_depositRequestDetails.amount,
_depositRequestDetails.sharesToMint
);
}
// delete deposit requests
_depositRequests.usersToDeposit.clear();
delete _shareClass.depositRequests[_settleDepositDetails.batchId];
emit SettleDepositBatch(
_settleDepositDetails.classId,
_settleDepositDetails.seriesId,
_settleDepositDetails.batchId,
_totalAmountToDeposit,
_totalSharesToMint
);
return (_totalAmountToDeposit, _totalSharesToMint);
}
/**
* @dev Internal function to settle all redeems for batches up to the current batch.
* @param _sd The storage struct.
* @param _settlementParams The parameters for the settlement.
*/
function _settleRedeem(AlephVaultStorageData storage _sd, SettlementParams calldata _settlementParams) internal {
// verify all conditions are satisfied to settle redeems
IAlephVault.ShareClass storage _shareClass = _sd.shareClasses[_settlementParams.classId];
uint48 _currentBatchId = _currentBatch(_sd);
uint48 _noticePeriod = _shareClass.shareClassParams.noticePeriod;
if (_settlementParams.toBatchId > _currentBatchId || _settlementParams.toBatchId < _noticePeriod) {
revert InvalidToBatchId();
}
uint48 _redeemSettleId = _shareClass.redeemSettleId;
uint48 _settleUptoBatchId = _settlementParams.toBatchId - _noticePeriod;
if (_settleUptoBatchId <= _redeemSettleId) {
revert NoRedeemsToSettle();
}
uint32 _lastConsolidatedSeriesId = _shareClass.lastConsolidatedSeriesId;
_validateNewTotalAssets(_shareClass.shareSeriesId, _lastConsolidatedSeriesId, _settlementParams.newTotalAssets);
if (_sd.isSettlementAuthEnabled) {
AuthLibrary.verifySettlementAuthSignature(
AuthLibrary.SETTLE_REDEEM,
_settlementParams.classId,
_settlementParams.toBatchId,
_sd.manager,
_settlementParams.newTotalAssets,
_sd.authSigner,
_settlementParams.authSignature
);
}
// accumalate fees if applicable
_accumulateFees(
_shareClass,
_settlementParams.classId,
_lastConsolidatedSeriesId,
_settlementParams.toBatchId,
_settlementParams.newTotalAssets
);
// consolidate series if required
_handleSeriesAccounting(
_shareClass, _settlementParams.classId, _lastConsolidatedSeriesId, _settlementParams.toBatchId
);
// settle redeems for each batch
uint256 _totalAmountToRedeem;
for (uint48 _batchId = _redeemSettleId; _batchId < _settleUptoBatchId; _batchId++) {
_totalAmountToRedeem += _settleRedeemForBatch(_sd, _shareClass, _settlementParams.classId, _batchId);
}
// revert if manager didnt fund the vault before settling redeems
uint256 _requiredVaultBalance = _totalAmountToRedeem + _sd.totalAmountToDeposit + _sd.totalAmountToWithdraw;
if (IERC20(_sd.underlyingToken).balanceOf(address(this)) < _requiredVaultBalance) {
revert InsufficientAssetsToSettle(_requiredVaultBalance);
}
_shareClass.redeemSettleId = _settleUptoBatchId;
_sd.totalAmountToWithdraw += _totalAmountToRedeem;
emit SettleRedeem(_settlementParams.classId, _redeemSettleId, _settlementParams.toBatchId);
}
/**
* @dev Internal function to settle redeems for a specific batch.
* @param _sd The storage struct.
* @param _shareClass The share class storage reference.
* @param _classId The id of the class.
* @param _batchId The id of the batch.
* @return _totalAmountToRedeem The total amount to redeem in this batch.
*/
function _settleRedeemForBatch(
AlephVaultStorageData storage _sd,
IAlephVault.ShareClass storage _shareClass,
uint8 _classId,
uint48 _batchId
) internal returns (uint256 _totalAmountToRedeem) {
IAlephVault.RedeemRequests storage _redeemRequests = _shareClass.redeemRequests[_batchId];
uint256 _len = _redeemRequests.usersToRedeem.length();
if (_len == 0) {
return 0;
}
// iterate through all requests in batch (one user can only make one request per batch)
for (uint256 _i; _i < _len; _i++) {
address _user = _redeemRequests.usersToRedeem.at(_i);
// calculate amount to redeem for the user
// redeem request value is the proportional amount user requested to redeem
// this amount can now be different from the original amount requested as the price per share
// in this cycle may have changed since the request was made due to pnl of the vault and fees
uint256 _amount = ERC4626Math.previewMintUnits(
_redeemRequests.redeemRequest[_user], _assetsPerClassOf(_shareClass, _classId, _user)
);
_shareClass.settleRedeemForUser(_classId, _batchId, _user, _amount);
_totalAmountToRedeem += _amount;
_sd.redeemableAmount[_user] += _amount;
// delete redeem request
delete _redeemRequests.redeemRequest[_user];
emit RedeemRequestSettled(_classId, _batchId, _user, _amount);
}
// delete redeem requests
_redeemRequests.usersToRedeem.clear();
delete _shareClass.redeemRequests[_batchId];
emit SettleRedeemBatch(_classId, _batchId, _totalAmountToRedeem);
}
/**
* @dev Internal function to force a redeem for a user.
* @param _sd The storage struct.
* @param _user The user to force a redeem for.
*/
function _forceRedeem(AlephVaultStorageData storage _sd, address _user) internal {
uint8 _shareClasses = _sd.shareClassesId;
uint48 _currentBatchId = _currentBatch(_sd);
uint256 _totalUserAssets;
uint256 _newDepositsToRedeem;
for (uint8 _classId = 1; _classId <= _shareClasses; _classId++) {
IAlephVault.ShareClass storage _shareClass = _sd.shareClasses[_classId];
uint48 _depositSettleId = _shareClass.depositSettleId;
uint48 _redeemSettleId = _shareClass.redeemSettleId;
for (
uint48 _batchId = _depositSettleId > _redeemSettleId ? _redeemSettleId : _depositSettleId;
_batchId <= _currentBatchId;
_batchId++
) {
if (_batchId >= _depositSettleId) {
IAlephVault.DepositRequests storage _depositRequest = _shareClass.depositRequests[_batchId];
uint256 _amount = _depositRequest.depositRequest[_user];
_newDepositsToRedeem += _amount;
_depositRequest.totalAmountToDeposit -= _amount;
_depositRequest.usersToDeposit.remove(_user);
delete _depositRequest.depositRequest[_user];
}
if (_batchId >= _redeemSettleId) {
IAlephVault.RedeemRequests storage _redeemRequest = _shareClass.redeemRequests[_batchId];
_redeemRequest.usersToRedeem.remove(_user);
delete _redeemRequest.redeemRequest[_user];
}
}
uint256 _userAssets = _assetsPerClassOf(_shareClass, _classId, _user);
_shareClass.settleRedeemForUser(_classId, _currentBatchId, _user, _userAssets);
_totalUserAssets += _userAssets;
}
uint256 _totalAssetsToSettle = _totalUserAssets + _newDepositsToRedeem;
_sd.totalAmountToWithdraw += _totalAssetsToSettle;
_sd.totalAmountToDeposit -= _newDepositsToRedeem;
_sd.redeemableAmount[_user] += _totalAssetsToSettle;
uint256 _requiredVaultBalance = _sd.totalAmountToWithdraw + _sd.totalAmountToDeposit;
if (IERC20(_sd.underlyingToken).balanceOf(address(this)) < _requiredVaultBalance) {
revert InsufficientAssetsToSettle(_requiredVaultBalance);
}
emit ForceRedeem(_currentBatchId, _user, _totalAssetsToSettle);
}
/**
* @dev Internal function to handle the series accounting.
* @param _shareClass The share class.
* @param _classId The id of the class.
* @param _lastConsolidatedSeriesId The id of the last consolidated series.
* @param _toBatchId The batch id in which to consolidate/create new series.
* @return _seriesId The series id in which to settle pending deposits.
* @dev this function is called before settling deposits/redeems to handle the series accounting.
* it consolidates outstanding series into the lead series if required. The return param is only
* used in settle deposits to get the series id in which to settle pending deposits.
* for redeems, this function is called only to handle consolidation if required.
*/
function _handleSeriesAccounting(
IAlephVault.ShareClass storage _shareClass,
uint8 _classId,
uint32 _lastConsolidatedSeriesId,
uint48 _toBatchId
) internal returns (uint32 _seriesId) {
// for non-incentive classes, all settlements take place in the lead series
if (_shareClass.shareClassParams.performanceFee > 0) {
uint32 _shareSeriesId = _shareClass.shareSeriesId;
// if new lead series highwatermark is not reached, deposit settlements must take place in a new series
// if a new highwater mark is reached in this cycle, it will be updated in _accumalateFees function
// hence, after fee accumalation process, the lead highwater mark is either greater than or equal to
// the lead price per share
if (
_shareClass.shareSeries[SeriesAccounting.LEAD_SERIES_ID].highWaterMark
> _leadPricePerShare(_shareClass, _classId)
) {
// we don't create a new series just yet because there might not be any deposit request to settle
// in this cycle
_seriesId = _shareSeriesId + 1;
} else if (_shareSeriesId > _lastConsolidatedSeriesId) {
// if new lead series highwatermark was reached in this cycle and their exists outstanding series,
// consolidate them into lead series
_shareClass.consolidateSeries(_classId, _shareSeriesId, _lastConsolidatedSeriesId, _toBatchId);
}
}
}
/**
* @dev Internal function to validate the new total assets.
* @param _shareSeriesId The id of the share series.
* @param _lastConsolidatedSeriesId The id of the last consolidated series.
* @param _newTotalAssets The new total assets after settlement.
*/
function _validateNewTotalAssets(
uint32 _shareSeriesId,
uint32 _lastConsolidatedSeriesId,
uint256[] calldata _newTotalAssets
) internal pure {
if (_newTotalAssets.length != _shareSeriesId - _lastConsolidatedSeriesId + 1) {
revert InvalidNewTotalAssets();
}
}
/**
* @dev Internal function to accumulate fees.
* @param _shareClass The share class to accumulate fees for.
* @param _classId The id of the class.
* @param _lastConsolidatedSeriesId The id of the last consolidated series.
* @param _toBatchId The batch id to settle deposits up to.
* @param _newTotalAssets The new total assets after settlement.
*/
function _accumulateFees(
IAlephVault.ShareClass storage _shareClass,
uint8 _classId,
uint32 _lastConsolidatedSeriesId,
uint48 _toBatchId,
uint256[] calldata _newTotalAssets
) internal {
uint48 _lastFeePaidId = _shareClass.lastFeePaidId;
if (_toBatchId > _lastFeePaidId) {
for (uint32 _i = 0; _i < _newTotalAssets.length; _i++) {
uint32 _seriesId = _i > SeriesAccounting.LEAD_SERIES_ID
? _lastConsolidatedSeriesId + _i
: SeriesAccounting.LEAD_SERIES_ID;
// update the series total assets and shares
_shareClass.shareSeries[_seriesId].totalAssets = _newTotalAssets[_i];
_shareClass.shareSeries[_seriesId].totalShares += _accumulateFeeShares(
_newTotalAssets[_i],
_shareClass.shareSeries[_seriesId].totalShares,
_toBatchId,
_lastFeePaidId,
_classId,
_seriesId
);
}
_shareClass.lastFeePaidId = _toBatchId;
}
}
/**
* @dev Internal function to get the accumulated fee shares.
* @param _newTotalAssets The new total assets after settlement.
* @param _totalShares The total shares in the vault.
* @param _toBatchId The batch id to settle deposits up to.
* @param _lastFeePaidId The last fee paid id.
* @param _classId The id of the class.
* @param _seriesId The id of the series.
* @return The accumulated fee shares to mint.
*/
function _accumulateFeeShares(
uint256 _newTotalAssets,
uint256 _totalShares,
uint48 _toBatchId,
uint48 _lastFeePaidId,
uint8 _classId,
uint32 _seriesId
) internal returns (uint256) {
if (_newTotalAssets == 0) {
return 0;
}
(bool _success, bytes memory _data) = _getStorage().moduleImplementations[ModulesLibrary.FEE_MANAGER]
.delegatecall(
abi.encodeCall(
IFeeManager.accumulateFees,
(_classId, _seriesId, _toBatchId, _lastFeePaidId, _newTotalAssets, _totalShares)
)
);
if (!_success) {
revert DelegateCallFailed(_data);
}
return abi.decode(_data, (uint256));
}
}
"
},
"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";
import {Math} from "../math/Math.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;
* }
* ```
*
* The following types are supported:
*
* - `bytes32` (`Bytes32Set`) since v3.3.0
* - `address` (`AddressSet`) since v3.3.0
* - `uint256` (`UintSet`) since v3.3.0
* - `string` (`StringSet`) since v5.4.0
* - `bytes` (`BytesSet`) since v5.4.0
*
* [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 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;
}
/**
* @dev Return a slice of the 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, uint256 start, uint256 end) private view returns (bytes32[] memory) {
unchecked {
end = Math.min(end, _length(set));
start = Math.min(start, end);
uint256 len = end - start;
bytes32[] memory result = new bytes32[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
// 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 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(Bytes32Set storage set) internal {
_clear(set._inner);
}
/**
* @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;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the 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, uint256 start, uint256 end) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
bytes32[] memory result;
assembly ("memory-safe") {
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 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(AddressSet storage set) internal {
_clear(set._inner);
}
/**
* @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;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the 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, uint256 start, uint256 end) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
address[] memory result;
assembly ("memory-safe") {
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 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(UintSet storage set) internal {
_clear(set._inner);
}
/**
* @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;
assembly ("memory-safe") {
result := store
}
return result;
}
/**
* @dev Return a slice of the 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, uint256 start, uint256 end) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner, start, end);
uint256[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
struct StringSet {
// Storage of set values
string[] _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(string 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(StringSet storage self, string memory value) internal returns (bool) {
if (!contains(self, value)) {
self._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
self._positions[value] = self._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(StringSet storage self, string memory value) internal returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = self._positions[value];
if (position != 0) {
// Equivalent to contains(self, 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 = self._values.length - 1;
if (valueIndex != lastIndex) {
string memory lastValue = self._values[lastIndex];
// Move the lastValue to the index where the value to delete is
self._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
self._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
self._values.pop();
// Delete the tracked position for the deleted slot
delete self._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(StringSet storage set) internal {
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(StringSet storage self, string memory value) internal view returns (bool) {
return self._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(StringSet storage self) internal view returns (uint256) {
return self._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(StringSet storage self, uint256 index) internal view returns (string memory) {
return self._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(StringSet storage self) internal view returns (string[] memory) {
return self._values;
}
/**
* @dev Return a slice of the 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(StringSet storage set, uint256 start, uint256 end) internal view returns (string[] memory) {
unchecked {
end = Math.min(end, length(set));
start = Math.min(start, end);
uint256 len = end - start;
string[] memory result = new string[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
struct BytesSet {
// Storage of set values
bytes[] _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(bytes 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(BytesSet storage self, bytes memory value) internal returns (bool) {
if (!contains(self, value)) {
self._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
self._positions[value] = self._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(BytesSet storage self, bytes memory value) internal returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = self._positions[value];
if (position != 0) {
// Equivalent to contains(self, 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 = self._values.length - 1;
if (valueIndex != lastIndex) {
bytes memory lastValue = self._values[lastIndex];
// Move the lastValue to the index where the value to delete is
self._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
self._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
self._values.pop();
// Delete the tracked position for the deleted slot
delete self._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(BytesSet storage set) internal {
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(BytesSet storage self, bytes memory value) internal view returns (bool) {
return self._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(BytesSet storage self) internal view returns (uint256) {
return self._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(BytesSet storage self, uint256 index) internal view returns (bytes memory) {
return self._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(BytesSet storage self) internal view returns (bytes[] memory) {
return self._values;
}
/**
* @dev Return a slice of the 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(BytesSet storage set, uint256 start, uint256 end) internal view returns (bytes[] memory) {
unchecked {
end = Math.min(end, length(set));
start = Math.min(start, end);
uint256 len = end - start;
bytes[] memory result = new bytes[](len);
for (uint256 i = 0; i < len; ++i) {
result[i] = Arrays.unsafeAccess(set._values, start + i).value;
}
return result;
}
}
}
"
},
"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.4.16;
/**
* @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/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
Submitted on: 2025-10-15 09:29:10
Comments
Log in to comment.
No comments yet.