Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/compressors/WithdrawalCompressor.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundaiton, 2025.
pragma solidity ^0.8.23;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
import {ICreditAccountV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditAccountV3.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {OptionalCall} from "@gearbox-protocol/core-v3/contracts/libraries/OptionalCall.sol";
import {BaseCompressor} from "./BaseCompressor.sol";
import {IWithdrawalSubcompressor} from "../interfaces/IWithdrawalSubcompressor.sol";
import {IWithdrawalCompressor} from "../interfaces/IWithdrawalCompressor.sol";
import {
WithdrawableAsset,
RequestableWithdrawal,
ClaimableWithdrawal,
PendingWithdrawal
} from "../types/WithdrawalInfo.sol";
import {WithdrawalLib} from "../types/WithdrawalInfo.sol";
import {AP_WITHDRAWAL_COMPRESSOR} from "../libraries/Literals.sol";
contract WithdrawalCompressor is BaseCompressor, Ownable {
using WithdrawalLib for WithdrawableAsset[];
using WithdrawalLib for RequestableWithdrawal[];
using WithdrawalLib for ClaimableWithdrawal[];
using WithdrawalLib for PendingWithdrawal[];
uint256 public constant version = 3_10;
bytes32 public constant contractType = AP_WITHDRAWAL_COMPRESSOR;
mapping(bytes32 => bytes32) public withdrawableTypeToCompressorType;
mapping(bytes32 => address) public compressorTypeToCompressor;
constructor(address _owner, address addressProvider_) BaseCompressor(addressProvider_) {
_transferOwnership(_owner);
}
function getWithdrawableAssets(address creditManager) public view returns (WithdrawableAsset[] memory) {
uint256 collateralTokensCount = ICreditManagerV3(creditManager).collateralTokensCount();
WithdrawableAsset[] memory withdrawableAssets = new WithdrawableAsset[](0);
for (uint256 i = 0; i < collateralTokensCount; i++) {
address token = ICreditManagerV3(creditManager).getTokenByMask(1 << i);
address compressor = _getCompressorForToken(token);
if (compressor == address(0)) {
continue;
}
WithdrawableAsset[] memory assets =
IWithdrawalSubcompressor(compressor).getWithdrawableAssets(creditManager, token);
withdrawableAssets = withdrawableAssets.concat(assets);
}
return withdrawableAssets;
}
function getCurrentWithdrawals(address creditAccount)
external
view
returns (ClaimableWithdrawal[] memory, PendingWithdrawal[] memory)
{
address creditManager = ICreditAccountV3(creditAccount).creditManager();
uint256 collateralTokensCount = ICreditManagerV3(creditManager).collateralTokensCount();
ClaimableWithdrawal[] memory claimableWithdrawals = new ClaimableWithdrawal[](0);
PendingWithdrawal[] memory pendingWithdrawals = new PendingWithdrawal[](0);
for (uint256 i = 0; i < collateralTokensCount; i++) {
address token = ICreditManagerV3(creditManager).getTokenByMask(1 << i);
address compressor = _getCompressorForToken(token);
if (compressor == address(0) || IERC20(token).balanceOf(creditAccount) <= 1) {
continue;
}
(ClaimableWithdrawal[] memory cwCurrent, PendingWithdrawal[] memory pwCurrent) =
IWithdrawalSubcompressor(compressor).getCurrentWithdrawals(creditAccount, token);
claimableWithdrawals = claimableWithdrawals.concat(cwCurrent);
pendingWithdrawals = pendingWithdrawals.concat(pwCurrent);
}
return (claimableWithdrawals.filterEmpty(), pendingWithdrawals.filterEmpty());
}
function getWithdrawalRequestResult(address creditAccount, address token, uint256 amount)
external
view
returns (RequestableWithdrawal memory withdrawal)
{
uint256 balance = IERC20(token).balanceOf(creditAccount);
if (balance < amount) {
amount = balance;
}
address creditManager = ICreditAccountV3(creditAccount).creditManager();
address withdrawalToken = _getWithdrawalTokenForToken(creditManager, token);
address compressor = _getCompressorForToken(withdrawalToken);
if (compressor != address(0)) {
withdrawal = IWithdrawalSubcompressor(compressor).getWithdrawalRequestResult(
creditAccount, token, withdrawalToken, amount
);
}
return withdrawal;
}
function setSubcompressor(address subcompressor) external onlyOwner {
compressorTypeToCompressor[IVersion(subcompressor).contractType()] = subcompressor;
}
function setWithdrawableTypeToCompressorType(bytes32 withdrawableType, bytes32 compressorType) external onlyOwner {
withdrawableTypeToCompressorType[withdrawableType] = compressorType;
}
function _getWithdrawalTokenForToken(address creditManager, address token) internal view returns (address) {
WithdrawableAsset[] memory withdrawableAssets = getWithdrawableAssets(creditManager);
for (uint256 i = 0; i < withdrawableAssets.length; i++) {
if (withdrawableAssets[i].token == token) {
return withdrawableAssets[i].withdrawalPhantomToken;
}
}
return address(0);
}
function _getCompressorForToken(address token) internal view returns (address) {
return compressorTypeToCompressor[withdrawableTypeToCompressorType[_getContractType(token)]];
}
function _getContractType(address phantomToken) internal view returns (bytes32) {
(bool success, bytes memory result) =
OptionalCall.staticCallOptionalSafe(phantomToken, abi.encodeCall(IVersion.contractType, ()), 100_000);
if (success) {
return abi.decode(result, (bytes32));
} else {
return bytes32(0);
}
}
}
"
},
"lib/@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../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.
*
* By default, the owner account will be the one that deploys the contract. 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;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @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 {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @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 {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_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/@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
/// @title Version interface
/// @notice Defines contract version and type
interface IVersion {
/// @notice Contract version
function version() external view returns (uint256);
/// @notice Contract type
function contractType() external view returns (bytes32);
}
"
},
"lib/@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
import {IVersion} from "./base/IVersion.sol";
/// @notice Debt management type
/// - `INCREASE_DEBT` borrows additional funds from the pool, updates account's debt and cumulative interest index
/// - `DECREASE_DEBT` repays debt components (quota interest and fees -> base interest and fees -> debt principal)
/// and updates all corresponding state variables (base interest index, quota interest and fees, debt).
/// When repaying all the debt, ensures that account has no enabled quotas.
enum ManageDebtAction {
INCREASE_DEBT,
DECREASE_DEBT
}
/// @notice Collateral/debt calculation mode
/// - `GENERIC_PARAMS` returns generic data like account debt and cumulative indexes
/// - `DEBT_ONLY` is same as `GENERIC_PARAMS` but includes more detailed debt info, like accrued base/quota
/// interest and fees
/// - `FULL_COLLATERAL_CHECK_LAZY` checks whether account is sufficiently collateralized in a lazy fashion,
/// i.e. it stops iterating over collateral tokens once TWV reaches the desired target.
/// Since it may return underestimated TWV, it's only available for internal use.
/// - `DEBT_COLLATERAL` is same as `DEBT_ONLY` but also returns total value and total LT-weighted value of
/// account's tokens, this mode is used during account liquidation
/// - `DEBT_COLLATERAL_SAFE_PRICES` is same as `DEBT_COLLATERAL` but uses safe prices from price oracle
enum CollateralCalcTask {
GENERIC_PARAMS,
DEBT_ONLY,
FULL_COLLATERAL_CHECK_LAZY,
DEBT_COLLATERAL,
DEBT_COLLATERAL_SAFE_PRICES
}
struct CreditAccountInfo {
uint256 debt;
uint256 cumulativeIndexLastUpdate;
uint128 cumulativeQuotaInterest;
uint128 quotaFees;
uint256 enabledTokensMask;
uint16 flags;
uint64 lastDebtUpdate;
address borrower;
}
struct CollateralDebtData {
uint256 debt;
uint256 cumulativeIndexNow;
uint256 cumulativeIndexLastUpdate;
uint128 cumulativeQuotaInterest;
uint256 accruedInterest;
uint256 accruedFees;
uint256 totalDebtUSD;
uint256 totalValue;
uint256 totalValueUSD;
uint256 twvUSD;
uint256 enabledTokensMask;
uint256 quotedTokensMask;
address[] quotedTokens;
address _poolQuotaKeeper;
}
struct CollateralTokenData {
address token;
uint16 ltInitial;
uint16 ltFinal;
uint40 timestampRampStart;
uint24 rampDuration;
}
interface ICreditManagerV3Events {
/// @notice Emitted when new credit configurator is set
event SetCreditConfigurator(address indexed newConfigurator);
}
/// @title Credit manager V3 interface
interface ICreditManagerV3 is IVersion, ICreditManagerV3Events {
function pool() external view returns (address);
function underlying() external view returns (address);
function creditFacade() external view returns (address);
function creditConfigurator() external view returns (address);
function accountFactory() external view returns (address);
function name() external view returns (string memory);
// ------------------ //
// ACCOUNT MANAGEMENT //
// ------------------ //
function openCreditAccount(address onBehalfOf) external returns (address);
function closeCreditAccount(address creditAccount) external;
function liquidateCreditAccount(
address creditAccount,
CollateralDebtData calldata collateralDebtData,
address to,
bool isExpired
) external returns (uint256 remainingFunds, uint256 loss);
function manageDebt(address creditAccount, uint256 amount, uint256 enabledTokensMask, ManageDebtAction action)
external
returns (uint256 newDebt, uint256, uint256);
function addCollateral(address payer, address creditAccount, address token, uint256 amount)
external
returns (uint256);
function withdrawCollateral(address creditAccount, address token, uint256 amount, address to)
external
returns (uint256);
function externalCall(address creditAccount, address target, bytes calldata callData)
external
returns (bytes memory result);
function approveToken(address creditAccount, address token, address spender, uint256 amount) external;
// -------- //
// ADAPTERS //
// -------- //
function adapterToContract(address adapter) external view returns (address targetContract);
function contractToAdapter(address targetContract) external view returns (address adapter);
function execute(bytes calldata data) external returns (bytes memory result);
function approveCreditAccount(address token, uint256 amount) external;
function setActiveCreditAccount(address creditAccount) external;
function getActiveCreditAccountOrRevert() external view returns (address creditAccount);
// ----------------- //
// COLLATERAL CHECKS //
// ----------------- //
function priceOracle() external view returns (address);
function fullCollateralCheck(
address creditAccount,
uint256 enabledTokensMask,
uint256[] calldata collateralHints,
uint16 minHealthFactor,
bool useSafePrices
) external returns (uint256);
function isLiquidatable(address creditAccount, uint16 minHealthFactor) external view returns (bool);
function calcDebtAndCollateral(address creditAccount, CollateralCalcTask task)
external
view
returns (CollateralDebtData memory cdd);
// ------ //
// QUOTAS //
// ------ //
function poolQuotaKeeper() external view returns (address);
function quotedTokensMask() external view returns (uint256);
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota, uint96 maxQuota)
external
returns (uint256 tokensToEnable, uint256 tokensToDisable);
// --------------------- //
// CREDIT MANAGER PARAMS //
// --------------------- //
function maxEnabledTokens() external view returns (uint8);
function fees()
external
view
returns (
uint16 feeInterest,
uint16 feeLiquidation,
uint16 liquidationDiscount,
uint16 feeLiquidationExpired,
uint16 liquidationDiscountExpired
);
function collateralTokensCount() external view returns (uint8);
function getTokenMaskOrRevert(address token) external view returns (uint256 tokenMask);
function getTokenByMask(uint256 tokenMask) external view returns (address token);
function liquidationThresholds(address token) external view returns (uint16 lt);
function ltParams(address token)
external
view
returns (uint16 ltInitial, uint16 ltFinal, uint40 timestampRampStart, uint24 rampDuration);
function collateralTokenByMask(uint256 tokenMask)
external
view
returns (address token, uint16 liquidationThreshold);
// ------------ //
// ACCOUNT INFO //
// ------------ //
function creditAccountInfo(address creditAccount)
external
view
returns (
uint256 debt,
uint256 cumulativeIndexLastUpdate,
uint128 cumulativeQuotaInterest,
uint128 quotaFees,
uint256 enabledTokensMask,
uint16 flags,
uint64 lastDebtUpdate,
address borrower
);
function getBorrowerOrRevert(address creditAccount) external view returns (address borrower);
function flagsOf(address creditAccount) external view returns (uint16);
function setFlagFor(address creditAccount, uint16 flag, bool value) external;
function enabledTokensMaskOf(address creditAccount) external view returns (uint256);
function creditAccounts() external view returns (address[] memory);
function creditAccounts(uint256 offset, uint256 limit) external view returns (address[] memory);
function creditAccountsLen() external view returns (uint256);
// ------------- //
// CONFIGURATION //
// ------------- //
function addToken(address token) external;
function setCollateralTokenData(
address token,
uint16 ltInitial,
uint16 ltFinal,
uint40 timestampRampStart,
uint24 rampDuration
) external;
function setFees(
uint16 feeInterest,
uint16 feeLiquidation,
uint16 liquidationDiscount,
uint16 feeLiquidationExpired,
uint16 liquidationDiscountExpired
) external;
function setContractAllowance(address adapter, address targetContract) external;
function setCreditFacade(address creditFacade) external;
function setPriceOracle(address priceOracle) external;
function setCreditConfigurator(address creditConfigurator) external;
}
"
},
"lib/@gearbox-protocol/core-v3/contracts/interfaces/ICreditAccountV3.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
import {IVersion} from "./base/IVersion.sol";
/// @title Credit account V3 interface
interface ICreditAccountV3 is IVersion {
function factory() external view returns (address);
function creditManager() external view returns (address);
function safeTransfer(address token, address to, uint256 amount) external;
function execute(address target, bytes calldata data) external returns (bytes memory result);
function rescue(address target, bytes calldata data) external;
}
"
},
"lib/@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` 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 amount) external returns (bool);
}
"
},
"lib/@gearbox-protocol/core-v3/contracts/libraries/OptionalCall.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
/// @title Optional call library
/// @notice Implements a function that calls a contract that may not have an expected selector.
/// Handles the case where the contract has a fallback function that may or may not change state.
library OptionalCall {
function staticCallOptionalSafe(address target, bytes memory data, uint256 gasAllowance)
internal
view
returns (bool, bytes memory)
{
(bool success, bytes memory returnData) = target.staticcall{gas: gasAllowance}(data);
return returnData.length > 0 ? (success, returnData) : (false, returnData);
}
}
"
},
"contracts/compressors/BaseCompressor.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundaiton, 2025.
pragma solidity ^0.8.23;
import {LibString} from "@solady/utils/LibString.sol";
import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
import {IPoolQuotaKeeperV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPoolQuotaKeeperV3.sol";
import {IPoolV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPoolV3.sol";
import {OptionalCall} from "@gearbox-protocol/core-v3/contracts/libraries/OptionalCall.sol";
import {IAddressProvider} from "@gearbox-protocol/permissionless/contracts/interfaces/IAddressProvider.sol";
import {IContractsRegister} from "@gearbox-protocol/permissionless/contracts/interfaces/IContractsRegister.sol";
import {IMarketConfigurator} from "@gearbox-protocol/permissionless/contracts/interfaces/IMarketConfigurator.sol";
import {IMarketConfiguratorFactory} from
"@gearbox-protocol/permissionless/contracts/interfaces/IMarketConfiguratorFactory.sol";
import {
AP_MARKET_CONFIGURATOR_FACTORY,
NO_VERSION_CONTROL
} from "@gearbox-protocol/permissionless/contracts/libraries/ContractLiterals.sol";
import {CreditManagerFilter, MarketFilter} from "../types/Filters.sol";
abstract contract BaseCompressor {
using LibString for bytes32;
using LibString for string;
struct Pool {
address addr;
address configurator;
}
address public immutable addressProvider;
constructor(address addressProvider_) {
addressProvider = addressProvider_;
}
function _getAddress(bytes32 key) internal view returns (address) {
return IAddressProvider(addressProvider).getAddressOrRevert(key, NO_VERSION_CONTROL);
}
function _getLatestAddress(bytes32 key, uint256 minorVersion) internal view returns (address) {
uint256 latestPatch = IAddressProvider(addressProvider).getLatestPatchVersion(key, minorVersion);
return IAddressProvider(addressProvider).getAddressOrRevert(key, latestPatch);
}
function _appendPostfix(bytes32 contractType, address token) internal view returns (bytes32) {
(bool success,) = OptionalCall.staticCallOptionalSafe({
target: token,
data: abi.encodeWithSignature("basisPointsRate()"),
gasAllowance: 10000
});
if (success) return string.concat(contractType.fromSmallString(), "::USDT").toSmallString();
return contractType;
}
function _getPools(MarketFilter memory filter) internal view returns (Pool[] memory pools) {
address[] memory configurators = filter.configurators.length != 0
? filter.configurators
: IMarketConfiguratorFactory(_getAddress(AP_MARKET_CONFIGURATOR_FACTORY)).getMarketConfigurators();
// rough estimate of maximum number of pools
uint256 max;
for (uint256 i; i < configurators.length; ++i) {
address contractsRegister = IMarketConfigurator(configurators[i]).contractsRegister();
max += IContractsRegister(contractsRegister).getPools().length;
}
// allocate the array with maximum potentially needed size (total number of pools can be assumed to be
// relatively small and the function is only called once, so memory expansion cost is not an issue)
pools = new Pool[](max);
uint256 num;
for (uint256 i; i < configurators.length; ++i) {
address contractsRegister = IMarketConfigurator(configurators[i]).contractsRegister();
address[] memory poolsMC = IContractsRegister(contractsRegister).getPools();
for (uint256 j; j < poolsMC.length; ++j) {
address pool = poolsMC[j];
if (filter.pools.length != 0 && !_contains(filter.pools, pool)) continue;
if (filter.underlying != address(0) && IPoolV3(pool).asset() != filter.underlying) continue;
pools[num++] = Pool({addr: pool, configurator: configurators[i]});
}
}
// trim the array to its actual size
assembly {
mstore(pools, num)
}
}
function _getCreditManagers(CreditManagerFilter memory filter)
internal
view
returns (address[] memory creditManagers)
{
Pool[] memory pools = _getPools(MarketFilter(filter.configurators, filter.pools, filter.underlying));
// rough estimate of maximum number of credit managers
uint256 max;
for (uint256 i; i < pools.length; ++i) {
address cr = IMarketConfigurator(pools[i].configurator).contractsRegister();
max += IContractsRegister(cr).getCreditManagers(pools[i].addr).length;
}
// allocate the array with maximum potentially needed size (total number of credit managers can be assumed
// to be relatively small and the function is only called once, so memory expansion cost is not an issue)
creditManagers = new address[](max);
uint256 num;
for (uint256 i; i < pools.length; ++i) {
address cr = IMarketConfigurator(pools[i].configurator).contractsRegister();
address[] memory managers = IContractsRegister(cr).getCreditManagers(pools[i].addr);
for (uint256 j; j < managers.length; ++j) {
if (filter.creditManagers.length != 0 && !_contains(filter.creditManagers, managers[j])) continue;
creditManagers[num++] = managers[j];
}
}
// trim the array to its actual size
assembly {
mstore(creditManagers, num)
}
}
function _contains(address[] memory array, address element) internal pure returns (bool) {
uint256 len = array.length;
for (uint256 i; i < len; ++i) {
if (array[i] == element) return true;
}
return false;
}
function _getTokens(address pool) internal view returns (address[] memory tokens) {
address quotaKeeper = IPoolV3(pool).poolQuotaKeeper();
address[] memory quotedTokens = IPoolQuotaKeeperV3(quotaKeeper).quotedTokens();
uint256 numTokens = quotedTokens.length;
tokens = new address[](numTokens + 1);
tokens[0] = IPoolV3(pool).asset();
for (uint256 i; i < numTokens; ++i) {
tokens[i + 1] = quotedTokens[i];
}
}
function _getPriceOracle(address pool, address configurator) internal view returns (address) {
address contractsRegister = IMarketConfigurator(configurator).contractsRegister();
return IContractsRegister(contractsRegister).getPriceOracle(pool);
}
function _getLossPolicy(address pool, address configurator) internal view returns (address) {
address contractsRegister = IMarketConfigurator(configurator).contractsRegister();
return IContractsRegister(contractsRegister).getLossPolicy(pool);
}
}
"
},
"contracts/interfaces/IWithdrawalSubcompressor.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2025.
pragma solidity ^0.8.23;
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
import {
WithdrawableAsset,
RequestableWithdrawal,
ClaimableWithdrawal,
PendingWithdrawal
} from "../types/WithdrawalInfo.sol";
interface IWithdrawalSubcompressor is IVersion {
function getWithdrawableAssets(address creditManager, address token)
external
view
returns (WithdrawableAsset[] memory);
function getCurrentWithdrawals(address creditAccount, address token)
external
view
returns (ClaimableWithdrawal[] memory, PendingWithdrawal[] memory);
function getWithdrawalRequestResult(address creditAccount, address token, address withdrawalToken, uint256 amount)
external
view
returns (RequestableWithdrawal memory);
}
"
},
"contracts/interfaces/IWithdrawalCompressor.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2025.
pragma solidity ^0.8.23;
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
import {
WithdrawableAsset,
RequestableWithdrawal,
ClaimableWithdrawal,
PendingWithdrawal
} from "../types/WithdrawalInfo.sol";
interface IWithdrawalCompressor is IVersion {
function getWithdrawableAssets(address creditManager) external view returns (WithdrawableAsset[] memory);
function getCurrentWithdrawals(address creditAccount)
external
view
returns (ClaimableWithdrawal[] memory, PendingWithdrawal[] memory);
function getWithdrawalRequestResult(address creditAccount, address token, uint256 amount)
external
view
returns (RequestableWithdrawal memory);
}
"
},
"contracts/types/WithdrawalInfo.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2025.
pragma solidity ^0.8.23;
import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol";
struct WithdrawalOutput {
address token;
bool isDelayed;
uint256 amount;
}
struct WithdrawableAsset {
address token;
address withdrawalPhantomToken;
address underlying;
uint256 withdrawalLength;
}
struct RequestableWithdrawal {
address token;
uint256 amountIn;
WithdrawalOutput[] outputs;
MultiCall[] requestCalls;
uint256 claimableAt;
}
struct ClaimableWithdrawal {
address token;
address withdrawalPhantomToken;
uint256 withdrawalTokenSpent;
WithdrawalOutput[] outputs;
MultiCall[] claimCalls;
}
struct PendingWithdrawal {
address token;
address withdrawalPhantomToken;
WithdrawalOutput[] expectedOutputs;
uint256 claimableAt;
}
library WithdrawalLib {
function push(WithdrawableAsset[] memory w, WithdrawableAsset memory asset)
internal
pure
returns (WithdrawableAsset[] memory)
{
WithdrawableAsset[] memory newWithdrawableAssets = new WithdrawableAsset[](w.length + 1);
for (uint256 i = 0; i < w.length; i++) {
newWithdrawableAssets[i] = w[i];
}
newWithdrawableAssets[w.length] = asset;
return newWithdrawableAssets;
}
function concat(WithdrawableAsset[] memory w0, WithdrawableAsset[] memory w1)
internal
pure
returns (WithdrawableAsset[] memory)
{
WithdrawableAsset[] memory newWithdrawableAssets = new WithdrawableAsset[](w0.length + w1.length);
for (uint256 i = 0; i < w0.length; i++) {
newWithdrawableAssets[i] = w0[i];
}
for (uint256 i = 0; i < w1.length; i++) {
newWithdrawableAssets[w0.length + i] = w1[i];
}
return newWithdrawableAssets;
}
function concat(RequestableWithdrawal[] memory w0, RequestableWithdrawal[] memory w1)
internal
pure
returns (RequestableWithdrawal[] memory)
{
RequestableWithdrawal[] memory withdrawals = new RequestableWithdrawal[](w0.length + w1.length);
for (uint256 i = 0; i < w0.length; i++) {
withdrawals[i] = w0[i];
}
for (uint256 i = 0; i < w1.length; i++) {
withdrawals[w0.length + i] = w1[i];
}
return withdrawals;
}
function push(ClaimableWithdrawal[] memory w, ClaimableWithdrawal memory withdrawal)
internal
pure
returns (ClaimableWithdrawal[] memory)
{
ClaimableWithdrawal[] memory newClaimableWithdrawals = new ClaimableWithdrawal[](w.length + 1);
for (uint256 i = 0; i < w.length; i++) {
newClaimableWithdrawals[i] = w[i];
}
newClaimableWithdrawals[w.length] = withdrawal;
return newClaimableWithdrawals;
}
function concat(ClaimableWithdrawal[] memory w0, ClaimableWithdrawal[] memory w1)
internal
pure
returns (ClaimableWithdrawal[] memory)
{
ClaimableWithdrawal[] memory withdrawals = new ClaimableWithdrawal[](w0.length + w1.length);
for (uint256 i = 0; i < w0.length; i++) {
withdrawals[i] = w0[i];
}
for (uint256 i = 0; i < w1.length; i++) {
withdrawals[w0.length + i] = w1[i];
}
return withdrawals;
}
function push(PendingWithdrawal[] memory w, PendingWithdrawal memory withdrawal)
internal
pure
returns (PendingWithdrawal[] memory)
{
PendingWithdrawal[] memory newPendingWithdrawals = new PendingWithdrawal[](w.length + 1);
for (uint256 i = 0; i < w.length; i++) {
newPendingWithdrawals[i] = w[i];
}
newPendingWithdrawals[w.length] = withdrawal;
return newPendingWithdrawals;
}
function concat(PendingWithdrawal[] memory w0, PendingWithdrawal[] memory w1)
internal
pure
returns (PendingWithdrawal[] memory)
{
PendingWithdrawal[] memory withdrawals = new PendingWithdrawal[](w0.length + w1.length);
for (uint256 i = 0; i < w0.length; i++) {
withdrawals[i] = w0[i];
}
for (uint256 i = 0; i < w1.length; i++) {
withdrawals[w0.length + i] = w1[i];
}
return withdrawals;
}
function filterEmpty(PendingWithdrawal[] memory pendingWithdrawals)
internal
pure
returns (PendingWithdrawal[] memory)
{
PendingWithdrawal[] memory filteredPendingWithdrawals = new PendingWithdrawal[](0);
for (uint256 i = 0; i < pendingWithdrawals.length; i++) {
if (pendingWithdrawals[i].expectedOutputs.length > 0) {
for (uint256 j = 0; j < pendingWithdrawals[i].expectedOutputs.length; j++) {
if (pendingWithdrawals[i].expectedOutputs[j].amount > 0) {
filteredPendingWithdrawals = push(filteredPendingWithdrawals, pendingWithdrawals[i]);
break;
}
}
}
}
return filteredPendingWithdrawals;
}
function filterEmpty(ClaimableWithdrawal[] memory claimableWithdrawals)
internal
pure
returns (ClaimableWithdrawal[] memory)
{
ClaimableWithdrawal[] memory filteredClaimableWithdrawals = new ClaimableWithdrawal[](0);
for (uint256 i = 0; i < claimableWithdrawals.length; i++) {
if (claimableWithdrawals[i].outputs.length > 0) {
for (uint256 j = 0; j < claimableWithdrawals[i].outputs.length; j++) {
if (claimableWithdrawals[i].outputs[j].amount > 0) {
filteredClaimableWithdrawals = push(filteredClaimableWithdrawals, claimableWithdrawals[i]);
break;
}
}
}
}
return filteredClaimableWithdrawals;
}
}
"
},
"contracts/libraries/Literals.sol": {
"content": "// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2025.
pragma solidity ^0.8.23;
bytes32 constant AP_CREDIT_ACCOUNT_COMPRESSOR = "GLOBAL::ACCOUNT_COMPRESSOR";
bytes32 constant AP_CREDIT_SUITE_COMPRESSOR = "GLOBAL::CREDIT_SUITE_COMPRESSOR";
bytes32 constant AP_GAUGE_COMPRESSOR = "GLOBAL::GAUGE_COMPRESSOR";
bytes32 constant AP_MARKET_COMPRESSOR = "GLOBAL::MARKET_COMPRESSOR";
bytes32 constant AP_PERIPHERY_COMPRESSOR = "GLOBAL::PERIPHERY_COMPRESSOR";
bytes32 constant AP_PRICE_FEED_COMPRESSOR = "GLOBAL::PRICE_FEED_COMPRESSOR";
bytes32 constant AP_REWARDS_COMPRESSOR = "GLOBAL::REWARDS_COMPRESSOR";
bytes32 constant AP_TOKEN_COMPRESSOR = "GLOBAL::TOKEN_COMPRESSOR";
bytes32 constant AP_WITHDRAWAL_COMPRESSOR = "GLOBAL::WITHDRAWAL_COMPRESSOR";
"
},
"lib/@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"lib/@solady/src/utils/LibString.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for converting numbers into strings and other string operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
///
/// @dev Note:
/// For performance and bytecode compactness, most of the string operations are restricted to
/// byte strings (7-bit ASCII), except where otherwise specified.
/// Usage of byte string operations on charsets with runes spanning two or more bytes
/// can lead to undefined behavior.
library LibString {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The length of the output is too small to contain all the hex digits.
error HexLengthInsufficient();
/// @dev The length of the string is more than 32 bytes.
error TooBigForSmallString();
/// @dev The input string must be a 7-bit ASCII.
error StringNot7BitASCII();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when the `search` is not found in the string.
uint256 internal constant NOT_FOUND = type(uint256).max;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant ALPHANUMERIC_7_BIT_ASCII = 0x7fffffe07fffffe03ff000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant LETTERS_7_BIT_ASCII = 0x7fffffe07fffffe0000000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'.
uint128 internal constant LOWERCASE_7_BIT_ASCII = 0x7fffffe000000000000000000000000;
/// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
uint128 internal constant UPPERCASE_7_BIT_ASCII = 0x7fffffe0000000000000000;
/// @dev Lookup for '0123456789'.
uint128 internal constant DIGITS_7_BIT_ASCII = 0x3ff000000000000;
/// @dev Lookup for '0123456789abcdefABCDEF'.
uint128 internal constant HEXDIGITS_7_BIT_ASCII = 0x7e0000007e03ff000000000000;
/// @dev Lookup for '01234567'.
uint128 internal constant OCTDIGITS_7_BIT_ASCII = 0xff000000000000;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \ \
\r\x0b\x0c'.
uint128 internal constant PRINTABLE_7_BIT_ASCII = 0x7fffffffffffffffffffffff00003e00;
/// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.
uint128 internal constant PUNCTUATION_7_BIT_ASCII = 0x78000001f8000001fc00fffe00000000;
/// @dev Lookup for ' \ \
\r\x0b\x0c'.
uint128 internal constant WHITESPACE_7_BIT_ASCII = 0x100003e00;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the base 10 decimal representation of `value`.
function toString(uint256 value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
// The maximum value of a uint256 contains 78 digits (1 byte per digit), but
// we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
// We will need 1 word for the trailing zeros padding, 1 word for the length,
// and 3 words for a maximum of 78 digits.
str := add(mload(0x40), 0x80)
// Update the free memory pointer to allocate.
mstore(0x40, add(str, 0x20))
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end of the memory to calculate the length later.
let end := str
let w := not(0) // Tsk.
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
str := add(str, w) // `sub(str, 1)`.
// Write the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(str, add(48, mod(temp, 10)))
// Keep dividing `temp` until zero.
temp := div(temp, 10)
if iszero(temp) { break }
}
let length := sub(end, str)
// Move the pointer 32 bytes leftwards to make room for the length.
str := sub(str, 0x20)
// Store the length.
mstore(str, length)
}
}
/// @dev Returns the base 10 decimal representation of `value`.
function toString(int256 value) internal pure returns (string memory str) {
if (value >= 0) {
return toString(uint256(value));
}
unchecked {
str = toString(~uint256(value) + 1);
}
/// @solidity memory-safe-assembly
assembly {
// We still have some spare memory space on the left,
// as we have allocated 3 words (96 bytes) for up to 78 digits.
let length := mload(str) // Load the string length.
mstore(str, 0x2d) // Store the '-' character.
str := sub(str, 1) // Move back the string pointer by a byte.
mstore(str, add(length, 1)) // Update the string length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HEXADECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `length` bytes.
/// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
/// giving a total length of `length * 2 + 2` bytes.
/// Reverts if `length` is too small for the output to contain all the digits.
function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value, length);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Write the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Write the length.
}
}
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `length` bytes.
/// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
/// giving a total length of `length * 2` bytes.
/// Reverts if `length` is too small for the output to contain all the digits.
function toHexStringNoPrefix(uint256 value, uint256 length)
internal
pure
returns (string memory str)
{
/// @solidity memory-safe-assembly
assembly {
// We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes
// for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length.
// We add 0x20 to the total and round down to a multiple of 0x20.
// (0x20 + 0x20 + 0x02 + 0x20) = 0x62.
str := add(mload(0x40), and(add(shl(1, length), 0x42), not(0x1f)))
// Allocate the memory.
mstore(0x40, add(str, 0x20))
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end to calculate the length later.
let end := str
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
let start := sub(str, add(length, length))
let w := not(1) // Tsk.
let temp := value
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for {} 1 {} {
str := add(str, w) // `sub(str, 2)`.
mstore8(add(str, 1), mload(and(temp, 15)))
mstore8(str, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(xor(str, start)) { break }
}
if temp {
mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`.
revert(0x1c, 0x04)
}
// Compute the string's length.
let strLength := sub(end, str)
// Move the pointer and write the length.
str := sub(str, 0x20)
mstore(str, strLength)
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
/// As address are 20 bytes long, the output will left-padded to have
/// a length of `20 * 2 + 2` bytes.
function toHexString(uint256 value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Write the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Write the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x".
/// The output excludes leading "0" from the `toHexString` output.
/// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`.
function toMinimalHexString(uint256 value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present.
let strLength := add(mload(str), 2) // Compute the length.
mstore(add(str, o), 0x3078) // Write the "0x" prefix, accounting for leading zero.
str := sub(add(str, o), 2) // Move the pointer, accounting for leading zero.
mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output excludes leading "0" from the `toHexStringNoPrefix` output.
/// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`.
function toMinimalHexStringNoPrefix(uint256 value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present.
let strLength := mload(str) // Get the length.
str := add(str, o) // Move the pointer, accounting for leading zero.
mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is encoded using 2 hexadecimal digits per byte.
/// As address are 20 bytes long, the output will left-padded to have
/// a length of `20 * 2` bytes.
function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
// We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
// 0x02 bytes for the prefix, and 0x40 bytes for the digits.
// The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0.
str := add(mload(0x40), 0x80)
// Allocate the memory.
mstore(0x40, add(str, 0x20))
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end to calculate the length later.
let end := str
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
let w := not(1) // Tsk.
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
str := add(str, w) // `sub(str, 2)`.
mstore8(add(str, 1), mload(and(temp, 15)))
mstore8(str, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(temp) { break }
}
// Compute the string's length.
let strLength := sub(end, str)
// Move the pointer and write the length.
str := sub(str, 0x20)
mstore(str, strLength)
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte,
/// and the alphabets are capitalized conditionally according to
/// https://eips.ethereum.org/EIPS/eip-55
function toHexStringChecksummed(address value) internal pure returns (string memory str) {
str = toHexString(value);
/// @solidity memory-safe-assembly
assembly {
let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...`
let o := add(str, 0x22)
let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... `
let t := shl(240, 136) // `0b10001000 << 240`
for { let i := 0 } 1 {} {
mstore(add(i, i), mul(t, byte(i, hashed)))
i := add(i, 1)
if eq(i, 20) { break }
}
mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask)))))
o := add(o, 0x20)
mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask)))))
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
function toHexString(address value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Write the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Write the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexStringNoPrefix(address value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
str := mload(0x40)
// Allocate the memory.
// We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
// 0x02 bytes for the prefix, and 0x28 bytes for the digits.
// The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80.
mstore(0x40, add(str, 0x80))
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
str := add(str, 2)
mstore(str, 40)
let o := add(str, 0x20)
mstore(add(o, 40), 0)
value := shl(96, value)
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let i := 0 } 1 {} {
let p := add(o, add(i, i))
let temp := byte(i, value)
mstore8(add(p, 1), mload(and(temp, 15)))
mstore8(p, mload(shr(4, temp)))
i := add(i, 1)
if eq(i, 20) { break }
}
}
}
/// @dev Returns the hex encoded string from the raw bytes.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexString(bytes memory raw) internal pure returns (string memory str) {
str = toHexStringNoPrefix(raw);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Write the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Write the length.
}
}
/// @dev Returns the hex encoded string from the raw bytes.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
let length := mload(raw)
str := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix.
mstore(str, add(length, length)) // Store the length of the output.
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
let o := add(str, 0x20)
let end := add(raw, length)
for {} iszero(eq(raw, end)) {} {
raw := add(raw, 1)
mstore8(add(o, 1), mload(and(mload(raw), 15)))
mstore8(o, mload(and(shr(4, mload(raw)), 15)))
o := add(o, 2)
}
mstore(o, 0) // Zeroize the slot after the string.
mstore(0x40, add(o, 0x20)) // Allocate the memory.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RUNE STRING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the number of UTF characters in the string.
function runeCount(string memory s) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
if mload(s) {
mstore(0x00, div(not(0), 255))
mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506)
let o := add(s, 0x20)
let end := add(o, mload(s))
for { result := 1 } 1 { result := add(result, 1) } {
o := add(o, byte(0, mload(shr(250, mload(o)))))
if iszero(lt(o, end)) { break }
}
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string.
/// (i.e. all characters codes are in [0..127])
function is7BitASCII(string memory s) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let mask := shl(7, div(not(0), 255))
result := 1
let n := mload(s)
if n {
let o := add(s, 0x20)
let end := add(o, n)
let last := mload(end)
mstore(end, 0)
for {} 1 {} {
if and(mask, mload(o)) {
result := 0
break
}
o := add(o, 0x20)
if iszero(lt(o, end)) { break }
}
mstore(end, last)
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string,
/// AND all characters are in the `allowed` lookup.
/// Note: If `s` is empty, returns true regardless of `allowed`.
function is7BitASCII(string memory s, uint128 allowed) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if mload(s) {
let allowed_ := shr(128, shl(128, allowed))
let o := add(s, 0x20)
let end := add(o, mload(s))
for {} 1 {} {
result := and(result, shr(byte(0, mload(o)), allowed_))
o := add(o, 1)
if iszero(and(result, lt(o, end))) { break }
}
}
}
}
/// @dev Converts the bytes in the 7-bit ASCII string `s` to
/// an allowed lookup for use in `is7BitASCII(s, allowed)`.
/// To save runtime gas, you can cache the result in an immutable variable.
function to7BitASCIIAllowedLookup(string memory s) internal pure returns (uint128 result) {
/// @solidity memory-safe-assembly
assembly {
if mload(s) {
let o := add(s, 0x20)
let end := add(o, mload(s))
for {} 1 {} {
result := or(result, shl(byte(0, mload(o)), 1))
o := add(o, 1)
if iszero(lt(o, end)) { break }
}
if shr(128, result) {
mstore(0x00, 0xc9807e0d) // `StringNot7BitASCII()`.
revert(0x1c, 0x04)
}
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BYTE STRING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance and bytecode compactness, byte string operations are restricted
// to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets.
// Usage of byte string operations on charsets with runes spanning two or more bytes
// can lead to undefined behavior.
/// @dev Returns `subject` all occurrences of `search` replaced with `replacement`.
function replace(string memory subject, string memory search, string memory replacement)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
let searchLength := mload(search)
let replacementLength := mload(replacement)
subject := add(subject, 0x20)
search := add(search, 0x20)
replacement := add(replacement, 0x20)
result := add(mload(0x40), 0x20)
let subjectEnd := add(subject, subjectLength)
if iszero(gt(searchLength, subjectLength)) {
let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1)
let h := 0
if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) }
let m := shl(3, sub(0x20, and(searchLength, 0x1f)))
let s := mload(search)
for
Submitted on: 2025-10-07 11:31:08
Comments
Log in to comment.
No comments yet.