// SPDX-License-Identifier: BUSL-1.1pragmasolidity 0.8.24;import"../Interfaces/IAddRemoveManagers.sol";
import"../Interfaces/IAddressesRegistry.sol";
import"../Interfaces/ITroveNFT.sol";
contractAddRemoveManagersisIAddRemoveManagers{
ITroveNFT internalimmutable troveNFT;
structRemoveManagerReceiver {
address manager;
address receiver;
}
/*
* Mapping from TroveId to granted address for operations that "give" money to the trove (add collateral, pay debt).
* Useful for instance for cold/hot wallet setups.
* If its value is zero address, any address is allowed to do those operations on behalf of trove owner.
* Otherwise, only the address in this mapping (and the trove owner) will be allowed.
* To restrict this permission to no one, trove owner should be set in this mapping.
*/mapping(uint256=>address) public addManagerOf;
/*
* Mapping from TroveId to granted addresses for operations that "withdraw" money from the trove (withdraw collateral, borrow),
* and for each of those addresses another address for the receiver of those withdrawn funds.
* Useful for instance for cold/hot wallet setups or for automations.
* Only the address in this mapping, if any, and the trove owner, will be allowed.
* Therefore, by default this permission is restricted to no one.
* If the receiver is zero, the owner is assumed as the receiver.
*/mapping(uint256=> RemoveManagerReceiver) public removeManagerReceiverOf;
errorEmptyManager();
errorNotBorrower();
errorNotOwnerNorAddManager();
errorNotOwnerNorRemoveManager();
eventTroveNFTAddressChanged(address _newTroveNFTAddress);
eventAddManagerUpdated(uint256indexed _troveId, address _newAddManager);
eventRemoveManagerAndReceiverUpdated(uint256indexed _troveId, address _newRemoveManager, address _newReceiver);
constructor(IAddressesRegistry _addressesRegistry) {
troveNFT = _addressesRegistry.troveNFT();
emit TroveNFTAddressChanged(address(troveNFT));
}
functionsetAddManager(uint256 _troveId, address _manager) external{
_requireCallerIsBorrower(_troveId);
_setAddManager(_troveId, _manager);
}
function_setAddManager(uint256 _troveId, address _manager) internal{
addManagerOf[_troveId] = _manager;
emit AddManagerUpdated(_troveId, _manager);
}
functionsetRemoveManager(uint256 _troveId, address _manager) external{
setRemoveManagerWithReceiver(_troveId, _manager, troveNFT.ownerOf(_troveId));
}
functionsetRemoveManagerWithReceiver(uint256 _troveId, address _manager, address _receiver) public{
_requireCallerIsBorrower(_troveId);
_setRemoveManagerAndReceiver(_troveId, _manager, _receiver);
}
function_setRemoveManagerAndReceiver(uint256 _troveId, address _manager, address _receiver) internal{
_requireNonZeroManagerUnlessWiping(_manager, _receiver);
removeManagerReceiverOf[_troveId].manager = _manager;
removeManagerReceiverOf[_troveId].receiver = _receiver;
emit RemoveManagerAndReceiverUpdated(_troveId, _manager, _receiver);
}
function_wipeAddRemoveManagers(uint256 _troveId) internal{
delete addManagerOf[_troveId];
delete removeManagerReceiverOf[_troveId];
emit AddManagerUpdated(_troveId, address(0));
emit RemoveManagerAndReceiverUpdated(_troveId, address(0), address(0));
}
function_requireNonZeroManagerUnlessWiping(address _manager, address _receiver) internalpure{
if (_manager ==address(0) && _receiver !=address(0)) {
revert EmptyManager();
}
}
function_requireCallerIsBorrower(uint256 _troveId) internalview{
if (msg.sender!= troveNFT.ownerOf(_troveId)) {
revert NotBorrower();
}
}
function_requireSenderIsOwnerOrAddManager(uint256 _troveId, address _owner) internalview{
address addManager = addManagerOf[_troveId];
if (msg.sender!= _owner && addManager !=address(0) &&msg.sender!= addManager) {
revert NotOwnerNorAddManager();
}
}
function_requireSenderIsOwnerOrRemoveManagerAndGetReceiver(uint256 _troveId, address _owner)
internalviewreturns (address)
{
address manager = removeManagerReceiverOf[_troveId].manager;
address receiver = removeManagerReceiverOf[_troveId].receiver;
if (msg.sender!= _owner &&msg.sender!= manager) {
revert NotOwnerNorRemoveManager();
}
if (receiver ==address(0) ||msg.sender!= manager) {
return _owner;
}
return receiver;
}
}
Contract Source Code
File 2 of 52: Address.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)pragmasolidity ^0.8.1;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/functionisContract(address account) internalviewreturns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0// for contracts in construction, since the code is only stored at the end// of the constructor execution.return account.code.length>0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/functionsendValue(addresspayable recipient, uint256 amount) internal{
require(address(this).balance>= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/functionfunctionCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/functionfunctionCallWithValue(address target, bytesmemory data, uint256 value) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(address(this).balance>= value, "Address: insufficient balance for call");
(bool success, bytesmemory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
(bool success, bytesmemory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/functionfunctionDelegateCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
(bool success, bytesmemory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/functionverifyCallResultFromTarget(address target,
bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
if (success) {
if (returndata.length==0) {
// only check isContract if the call was successful and the return data is empty// otherwise we already know that it was a contractrequire(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/functionverifyCallResult(bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalpurereturns (bytesmemory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function_revert(bytesmemory returndata, stringmemory errorMessage) privatepure{
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assembly/// @solidity memory-safe-assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.24;type BatchId isaddress;
using {equalsas==, notEqualsas!=, isZero, isNotZero} forBatchIdglobal;
functionequals(BatchId a, BatchId b) purereturns (bool) {
return BatchId.unwrap(a) == BatchId.unwrap(b);
}
functionnotEquals(BatchId a, BatchId b) purereturns (bool) {
return!(a == b);
}
functionisZero(BatchId x) purereturns (bool) {
return x == BATCH_ID_ZERO;
}
functionisNotZero(BatchId x) purereturns (bool) {
return!x.isZero();
}
BatchId constant BATCH_ID_ZERO = BatchId.wrap(address(0));
Contract Source Code
File 5 of 52: Constants.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity 0.8.24;addressconstant ZERO_ADDRESS =address(0);
uint256constant MAX_UINT256 =type(uint256).max;
uint256constant DECIMAL_PRECISION =1e18;
uint256constant _100pct = DECIMAL_PRECISION;
uint256constant _1pct = DECIMAL_PRECISION /100;
// Amount of ETH to be locked in gas pool on opening trovesuint256constant ETH_GAS_COMPENSATION =0.0375ether;
// Liquidationuint256constant MIN_LIQUIDATION_PENALTY_SP =5e16; // 5%uint256constant MAX_LIQUIDATION_PENALTY_REDISTRIBUTION =20e16; // 20%// Fraction of collateral awarded to liquidatoruint256constant COLL_GAS_COMPENSATION_DIVISOR =200; // dividing by 200 yields 0.5%uint256constant COLL_GAS_COMPENSATION_CAP =2ether; // Max coll gas compensation capped at 2 ETH// Minimum amount of net Bold debt a trove must haveuint256constant MIN_DEBT =2000e18;
uint256constant MIN_ANNUAL_INTEREST_RATE = _1pct /2; // 0.5%uint256constant MAX_ANNUAL_INTEREST_RATE =250* _1pct;
// Batch management paramsuint128constant MAX_ANNUAL_BATCH_MANAGEMENT_FEE =uint128(_100pct /10); // 10%uint128constant MIN_INTEREST_RATE_CHANGE_PERIOD =1hours; // only applies to batch managers / batched Trovesuint256constant REDEMPTION_FEE_FLOOR = _1pct /2; // 0.5%// For the debt / shares ratio to increase by a factor 1e9// at a average annual debt increase (compounded interest + fees) of 10%, it would take more than 217 years (log(1e9)/log(1.1))// at a average annual debt increase (compounded interest + fees) of 50%, it would take more than 51 years (log(1e9)/log(1.5))// The increase pace could be forced to be higher through an inflation attack,// but precisely the fact that we have this max value now prevents the attackuint256constant MAX_BATCH_SHARES_RATIO =1e9;
// Half-life of 6h. 6h = 360 min// (1/2) = d^360 => d = (1/2)^(1/360)uint256constant REDEMPTION_MINUTE_DECAY_FACTOR =998076443575628800;
// BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a redemption.// Corresponds to (1 / ALPHA) in the white paper.uint256constant REDEMPTION_BETA =1;
// To prevent redemptions unless Bold depegs below 0.95 and allow the system to take offuint256constant INITIAL_BASE_RATE = _100pct; // 100% initial redemption rate// Discount to be used once the shutdown thas been triggereduint256constant URGENT_REDEMPTION_BONUS =2e16; // 2%uint256constant ONE_MINUTE =1minutes;
uint256constant ONE_YEAR =365days;
uint256constant UPFRONT_INTEREST_PERIOD =7days;
uint256constant INTEREST_RATE_ADJ_COOLDOWN =7days;
uint256constant SP_YIELD_SPLIT =75* _1pct; // 75%// Dummy contract that lets legacy Hardhat tests query some of the constantscontractConstants{
uint256publicconstant _ETH_GAS_COMPENSATION = ETH_GAS_COMPENSATION;
uint256publicconstant _MIN_DEBT = MIN_DEBT;
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/interfaceIERC165{
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/functionsupportsInterface(bytes4 interfaceId) externalviewreturns (bool);
}
Contract Source Code
File 17 of 52: IERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed 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.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (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.
*/functiontransfer(address to, uint256 amount) externalreturns (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.
*/functionallowance(address owner, address spender) externalviewreturns (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.
*/functionapprove(address spender, uint256 amount) externalreturns (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.
*/functiontransferFrom(addressfrom, address to, uint256 amount) externalreturns (bool);
}
Contract Source Code
File 18 of 52: IERC20Metadata.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)pragmasolidity ^0.8.0;import"../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/interfaceIERC20MetadataisIERC20{
/**
* @dev Returns the name of the token.
*/functionname() externalviewreturns (stringmemory);
/**
* @dev Returns the symbol of the token.
*/functionsymbol() externalviewreturns (stringmemory);
/**
* @dev Returns the decimals places of the token.
*/functiondecimals() externalviewreturns (uint8);
}
Contract Source Code
File 19 of 52: IERC20Permit.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/interfaceIERC20Permit{
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/functionnonces(address owner) externalviewreturns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/// solhint-disable-next-line func-name-mixedcasefunctionDOMAIN_SEPARATOR() externalviewreturns (bytes32);
}
Contract Source Code
File 20 of 52: IERC5267.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol)pragmasolidity ^0.8.0;interfaceIERC5267{
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/eventEIP712DomainChanged();
/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/functioneip712Domain()
externalviewreturns (bytes1 fields,
stringmemory name,
stringmemory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}
Contract Source Code
File 21 of 52: IERC721.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)pragmasolidity ^0.8.0;import"../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/interfaceIERC721isIERC165{
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/eventTransfer(addressindexedfrom, addressindexed to, uint256indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/eventApproval(addressindexed owner, addressindexed approved, uint256indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/eventApprovalForAll(addressindexed owner, addressindexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/functionbalanceOf(address owner) externalviewreturns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/functionownerOf(uint256 tokenId) externalviewreturns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/functionsafeTransferFrom(addressfrom, address to, uint256 tokenId, bytescalldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/functionsafeTransferFrom(addressfrom, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/functiontransferFrom(addressfrom, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/functionapprove(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/functionsetApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/functiongetApproved(uint256 tokenId) externalviewreturns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/functionisApprovedForAll(address owner, address operator) externalviewreturns (bool);
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import"./IActivePool.sol";
import"./ILiquityBase.sol";
import"./IBoldToken.sol";
import"./ITroveManager.sol";
import"./IBoldRewardsReceiver.sol";
/*
* The Stability Pool holds Bold tokens deposited by Stability Pool depositors.
*
* When a trove is liquidated, then depending on system conditions, some of its Bold debt gets offset with
* Bold in the Stability Pool: that is, the offset debt evaporates, and an equal amount of Bold tokens in the Stability Pool is burned.
*
* Thus, a liquidation causes each depositor to receive a Bold loss, in proportion to their deposit as a share of total deposits.
* They also receive an Coll gain, as the collateral of the liquidated trove is distributed among Stability depositors,
* in the same proportion.
*
* When a liquidation occurs, it depletes every deposit by the same fraction: for example, a liquidation that depletes 40%
* of the total Bold in the Stability Pool, depletes 40% of each deposit.
*
* A deposit that has experienced a series of liquidations is termed a "compounded deposit": each liquidation depletes the deposit,
* multiplying it by some factor in range ]0,1[
*
* Please see the implementation spec in the proof document, which closely follows on from the compounded deposit / Coll gain derivations:
* https://github.com/liquity/liquity/blob/master/papers/Scalable_Reward_Distribution_with_Compounding_Stakes.pdf
*
*/interfaceIStabilityPoolisILiquityBase, IBoldRewardsReceiver{
functionboldToken() externalviewreturns (IBoldToken);
functiontroveManager() externalviewreturns (ITroveManager);
/* provideToSP():
* - Calculates depositor's Coll gain
* - Calculates the compounded deposit
* - Increases deposit, and takes new snapshots of accumulators P and S
* - Sends depositor's accumulated Coll gains to depositor
*/functionprovideToSP(uint256 _amount, bool _doClaim) external;
/* withdrawFromSP():
* - Calculates depositor's Coll gain
* - Calculates the compounded deposit
* - Sends the requested BOLD withdrawal to depositor
* - (If _amount > userDeposit, the user withdraws all of their compounded deposit)
* - Decreases deposit by withdrawn amount and takes new snapshots of accumulators P and S
*/functionwithdrawFromSP(uint256 _amount, bool doClaim) external;
functionclaimAllCollGains() external;
/*
* Initial checks:
* - Caller is TroveManager
* ---
* Cancels out the specified debt against the Bold contained in the Stability Pool (as far as possible)
* and transfers the Trove's collateral from ActivePool to StabilityPool.
* Only called by liquidation functions in the TroveManager.
*/functionoffset(uint256 _debt, uint256 _coll) external;
functiondeposits(address _depositor) externalviewreturns (uint256 initialValue);
functionstashedColl(address _depositor) externalviewreturns (uint256);
/*
* Returns the total amount of Coll held by the pool, accounted in an internal variable instead of `balance`,
* to exclude edge cases like Coll received from a self-destruct.
*/functiongetCollBalance() externalviewreturns (uint256);
/*
* Returns Bold held in the pool. Changes when users deposit/withdraw, and when Trove debt is offset.
*/functiongetTotalBoldDeposits() externalviewreturns (uint256);
functiongetYieldGainsOwed() externalviewreturns (uint256);
functiongetYieldGainsPending() externalviewreturns (uint256);
/*
* Calculates the Coll gain earned by the deposit since its last snapshots were taken.
*/functiongetDepositorCollGain(address _depositor) externalviewreturns (uint256);
/*
* Calculates the BOLD yield gain earned by the deposit since its last snapshots were taken.
*/functiongetDepositorYieldGain(address _depositor) externalviewreturns (uint256);
/*
* Calculates what `getDepositorYieldGain` will be if interest is minted now.
*/functiongetDepositorYieldGainWithPending(address _depositor) externalviewreturns (uint256);
/*
* Return the user's compounded deposit.
*/functiongetCompoundedBoldDeposit(address _depositor) externalviewreturns (uint256);
functionepochToScaleToS(uint128 _epoch, uint128 _scale) externalviewreturns (uint256);
functionepochToScaleToB(uint128 _epoch, uint128 _scale) externalviewreturns (uint256);
functionP() externalviewreturns (uint256);
functioncurrentScale() externalviewreturns (uint128);
functioncurrentEpoch() externalviewreturns (uint128);
}
//SPDX-License-Identifier: MITpragmasolidity ^0.8.12;// JSON utilities for base64 encoded ERC721 JSON metadata schemelibraryjson{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// @dev JSON requires that double quotes be escaped or JSONs will not build correctly/// string.concat also requires an escape, use \\" or the constant DOUBLE_QUOTES to represent " in JSON/////////////////////////////////////////////////////////////////////////////////////////////////////////////stringconstant DOUBLE_QUOTES ='\\"';
functionformattedMetadata(stringmemory name,
stringmemory description,
stringmemory svgImg,
stringmemory attributes
) internalpurereturns (stringmemory) {
returnstring.concat(
"data:application/json;base64,",
encode(
bytes(
string.concat(
"{",
_prop("name", name),
_prop("description", description),
_xmlImage(svgImg),
',"attributes":',
attributes,
"}"
)
)
)
);
}
function_xmlImage(stringmemory _svgImg) internalpurereturns (stringmemory) {
return _prop("image", string.concat("data:image/svg+xml;base64,", encode(bytes(_svgImg))), true);
}
function_prop(stringmemory _key, stringmemory _val) internalpurereturns (stringmemory) {
returnstring.concat('"', _key, '": ', '"', _val, '", ');
}
function_prop(stringmemory _key, stringmemory _val, bool last) internalpurereturns (stringmemory) {
if (last) {
returnstring.concat('"', _key, '": ', '"', _val, '"');
} else {
returnstring.concat('"', _key, '": ', '"', _val, '", ');
}
}
function_object(stringmemory _key, stringmemory _val) internalpurereturns (stringmemory) {
returnstring.concat('"', _key, '": ', "{", _val, "}");
}
/**
* taken from Openzeppelin
* @dev Base64 Encoding/Decoding Table
*/stringinternalconstant _TABLE ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* @dev Converts a `bytes` to its Bytes64 `string` representation.
*/functionencode(bytesmemory data) internalpurereturns (stringmemory) {
/**
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/if (data.length==0) return"";
// Loads the table into memorystringmemory table = _TABLE;
// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter// and split into 4 numbers of 6 bits.// The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up// - `data.length + 2` -> Round up// - `/ 3` -> Number of 3-bytes chunks// - `4 *` -> 4 characters for each chunkstringmemory result =newstring(4* ((data.length+2) /3));
assembly {
// Prepare the lookup table (skip the first "length" byte)let tablePtr :=add(table, 1)
// Prepare result pointer, jump over lengthlet resultPtr :=add(result, 32)
// Run over the input, 3 bytes at a timefor {
let dataPtr := data
let endPtr :=add(data, mload(data))
} lt(dataPtr, endPtr) {} {
// Advance 3 bytes
dataPtr :=add(dataPtr, 3)
let input :=mload(dataPtr)
// To write each character, shift the 3 bytes (18 bits) chunk// 4 times in blocks of 6 bits for each character (18, 12, 6, 0)// and apply logical AND with 0x3F which is the number of// the previous character in the ASCII table prior to the Base64 Table// The result is then added to the table to get the character to write,// and finally write it in the result pointer but with a left shift// of 256 (1 byte) - 8 (1 ASCII char) = 248 bitsmstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
resultPtr :=add(resultPtr, 1) // Advancemstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
resultPtr :=add(resultPtr, 1) // Advancemstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
resultPtr :=add(resultPtr, 1) // Advancemstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
resultPtr :=add(resultPtr, 1) // Advance
}
// When data `bytes` is not exactly 3 bytes long// it is padded with `=` characters at the endswitchmod(mload(data), 3)
case1 {
mstore8(sub(resultPtr, 1), 0x3d)
mstore8(sub(resultPtr, 2), 0x3d)
}
case2 { mstore8(sub(resultPtr, 1), 0x3d) }
}
return result;
}
}
// SPDX-License-Identifier: MITpragmasolidity 0.8.24;import"./WETHZapper.sol";
import"../Dependencies/Constants.sol";
import"./Interfaces/ILeverageZapper.sol";
// TODO: unwrap WETH in _returnLeftoverscontractLeverageWETHZapperisWETHZapper, ILeverageZapper{
constructor(IAddressesRegistry _addressesRegistry, IFlashLoanProvider _flashLoanProvider, IExchange _exchange)
WETHZapper(_addressesRegistry, _flashLoanProvider, _exchange)
{
// Approval of coll (WETH) to BorrowerOperations is done in parent WETHZapper// Approve Bold to exchange module (Coll is approved in parent WETHZapper)
boldToken.approve(address(_exchange), type(uint256).max);
}
functionopenLeveragedTroveWithRawETH(OpenLeveragedTroveParams calldata _params) externalpayable{
require(msg.value== ETH_GAS_COMPENSATION + _params.collAmount, "LZ: Wrong amount of ETH");
require(
_params.batchManager ==address(0) || _params.annualInterestRate ==0,
"LZ: Cannot choose interest if joining a batch"
);
// Set initial balances to make sure there are not lefovers
InitialBalances memory initialBalances;
_setInitialTokensAndBalances(WETH, boldToken, initialBalances);
// Convert ETH to WETH
WETH.deposit{value: msg.value}();
// Flash loan coll
flashLoanProvider.makeFlashLoan(
WETH, _params.flashLoanAmount, IFlashLoanProvider.Operation.OpenTrove, abi.encode(_params)
);
// return leftovers to user
_returnLeftovers(initialBalances);
}
// Callback from the flash loan providerfunctionreceiveFlashLoanOnOpenLeveragedTrove(
OpenLeveragedTroveParams calldata _params,
uint256 _effectiveFlashLoanAmount
) externaloverride{
require(msg.sender==address(flashLoanProvider), "LZ: Caller not FlashLoan provider");
uint256 totalCollAmount = _params.collAmount + _effectiveFlashLoanAmount;
// We compute boldAmount off-chain for efficiencyuint256 troveId;
// Open troveif (_params.batchManager ==address(0)) {
troveId = borrowerOperations.openTrove(
_params.owner,
_params.ownerIndex,
totalCollAmount,
_params.boldAmount,
_params.upperHint,
_params.lowerHint,
_params.annualInterestRate,
_params.maxUpfrontFee,
// Add this contract as add/receive manager to be able to fully adjust trove,// while keeping the same management functionalityaddress(this), // add manageraddress(this), // remove manageraddress(this) // receiver for remove manager
);
} else {
IBorrowerOperations.OpenTroveAndJoinInterestBatchManagerParams memory
openTroveAndJoinInterestBatchManagerParams = IBorrowerOperations
.OpenTroveAndJoinInterestBatchManagerParams({
owner: _params.owner,
ownerIndex: _params.ownerIndex,
collAmount: totalCollAmount,
boldAmount: _params.boldAmount,
upperHint: _params.upperHint,
lowerHint: _params.lowerHint,
interestBatchManager: _params.batchManager,
maxUpfrontFee: _params.maxUpfrontFee,
// Add this contract as add/receive manager to be able to fully adjust trove,// while keeping the same management functionality
addManager: address(this), // add manager
removeManager: address(this), // remove manager
receiver: address(this) // receiver for remove manager
});
troveId =
borrowerOperations.openTroveAndJoinInterestBatchManager(openTroveAndJoinInterestBatchManagerParams);
}
// Set add/remove managers
_setAddManager(troveId, _params.addManager);
_setRemoveManagerAndReceiver(troveId, _params.removeManager, _params.receiver);
// Swap Bold to Coll
exchange.swapFromBold(_params.boldAmount, _params.flashLoanAmount);
// Send coll back to return flash loan
WETH.transfer(address(flashLoanProvider), _params.flashLoanAmount);
// WETH reverts on failure: https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code
}
functionleverUpTrove(LeverUpTroveParams calldata _params) external{
address owner = troveNFT.ownerOf(_params.troveId);
address receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_params.troveId, owner);
// Set initial balances to make sure there are not lefovers
InitialBalances memory initialBalances;
_setInitialTokensBalancesAndReceiver(WETH, boldToken, initialBalances, receiver);
// Flash loan coll
flashLoanProvider.makeFlashLoan(
WETH, _params.flashLoanAmount, IFlashLoanProvider.Operation.LeverUpTrove, abi.encode(_params)
);
// return leftovers to user
_returnLeftovers(initialBalances);
}
// Callback from the flash loan providerfunctionreceiveFlashLoanOnLeverUpTrove(LeverUpTroveParams calldata _params, uint256 _effectiveFlashLoanAmount)
externaloverride{
require(msg.sender==address(flashLoanProvider), "LZ: Caller not FlashLoan provider");
// Adjust trove// With the received coll from flash loan, we increase both the trove coll and debt
borrowerOperations.adjustTrove(
_params.troveId,
_effectiveFlashLoanAmount, // flash loan amount minus feetrue, // _isCollIncrease
_params.boldAmount,
true, // _isDebtIncrease
_params.maxUpfrontFee
);
// Swap Bold to Coll// No need to use a min: if the obtained amount is not enough, the flash loan return below won’t be enough// And the flash loan provider will revert after this function exits// The frontend should calculate in advance the `_params.boldAmount` needed for this to work
exchange.swapFromBold(_params.boldAmount, _params.flashLoanAmount);
// Send coll back to return flash loan
WETH.transfer(address(flashLoanProvider), _params.flashLoanAmount);
}
functionleverDownTrove(LeverDownTroveParams calldata _params) external{
address owner = troveNFT.ownerOf(_params.troveId);
address receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_params.troveId, owner);
// Set initial balances to make sure there are not lefovers
InitialBalances memory initialBalances;
_setInitialTokensBalancesAndReceiver(WETH, boldToken, initialBalances, receiver);
// Flash loan coll
flashLoanProvider.makeFlashLoan(
WETH, _params.flashLoanAmount, IFlashLoanProvider.Operation.LeverDownTrove, abi.encode(_params)
);
// return leftovers to user
_returnLeftovers(initialBalances);
}
// Callback from the flash loan providerfunctionreceiveFlashLoanOnLeverDownTrove(LeverDownTroveParams calldata _params, uint256 _effectiveFlashLoanAmount)
externaloverride{
require(msg.sender==address(flashLoanProvider), "LZ: Caller not FlashLoan provider");
// Swap Coll from flash loan to Bold, so we can repay and downsize trove// We swap the flash loan minus the flash loan fee// The frontend should calculate in advance the `_params.minBoldAmount` to achieve the desired leverage ratio// (with some slippage tolerance)uint256 receivedBoldAmount = exchange.swapToBold(_effectiveFlashLoanAmount, _params.minBoldAmount);
// Adjust trove
borrowerOperations.adjustTrove(
_params.troveId,
_params.flashLoanAmount,
false, // _isCollIncrease
receivedBoldAmount,
false, // _isDebtIncrease0
);
// Send coll back to return flash loan
WETH.transfer(address(flashLoanProvider), _params.flashLoanAmount);
}
// As formulas are symmetrical, it can be used in both waysfunctionleverageRatioToCollateralRatio(uint256 _inputRatio) externalpurereturns (uint256) {
return _inputRatio * DECIMAL_PRECISION / (_inputRatio - DECIMAL_PRECISION);
}
}
Contract Source Code
File 43 of 52: LibString.sol
// SPDX-License-Identifier: MITpragmasolidity ^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.libraryLibString{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The length of the output is too small to contain all the hex digits.errorHexLengthInsufficient();
/// @dev The length of the string is more than 32 bytes.errorTooBigForSmallString();
/// @dev The input string must be a 7-bit ASCII.errorStringNot7BitASCII();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The constant returned when the `search` is not found in the string.uint256internalconstant NOT_FOUND =type(uint256).max;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant ALPHANUMERIC_7_BIT_ASCII =0x7fffffe07fffffe03ff000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant LETTERS_7_BIT_ASCII =0x7fffffe07fffffe0000000000000000;
/// @dev Lookup for 'abcdefghijklmnopqrstuvwxyz'.uint128internalconstant LOWERCASE_7_BIT_ASCII =0x7fffffe000000000000000000000000;
/// @dev Lookup for 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.uint128internalconstant UPPERCASE_7_BIT_ASCII =0x7fffffe0000000000000000;
/// @dev Lookup for '0123456789'.uint128internalconstant DIGITS_7_BIT_ASCII =0x3ff000000000000;
/// @dev Lookup for '0123456789abcdefABCDEF'.uint128internalconstant HEXDIGITS_7_BIT_ASCII =0x7e0000007e03ff000000000000;
/// @dev Lookup for '01234567'.uint128internalconstant OCTDIGITS_7_BIT_ASCII =0xff000000000000;
/// @dev Lookup for '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'.uint128internalconstant PRINTABLE_7_BIT_ASCII =0x7fffffffffffffffffffffff00003e00;
/// @dev Lookup for '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.uint128internalconstant PUNCTUATION_7_BIT_ASCII =0x78000001f8000001fc00fffe00000000;
/// @dev Lookup for ' \t\n\r\x0b\x0c'.uint128internalconstant WHITESPACE_7_BIT_ASCII =0x100003e00;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* DECIMAL OPERATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the base 10 decimal representation of `value`.functiontoString(uint256 value) internalpurereturns (stringmemory str) {
/// @solidity memory-safe-assemblyassembly {
// 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)
mstore(0x40, add(str, 0x20)) // Allocate the memory.mstore(str, 0) // Zeroize the slot after the string.let end := str // Cache the end of the memory to calculate the length later.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)`.// Store the character to the pointer.// The ASCII index of the '0' character is 48.mstore8(str, add(48, mod(temp, 10)))
temp :=div(temp, 10) // Keep dividing `temp` until zero.ifiszero(temp) { break }
}
let length :=sub(end, str)
str :=sub(str, 0x20) // Move the pointer 32 bytes back to make room for the length.mstore(str, length) // Store the length.
}
}
/// @dev Returns the base 10 decimal representation of `value`.functiontoString(int256 value) internalpurereturns (stringmemory str) {
if (value >=0) return toString(uint256(value));
unchecked {
str = toString(~uint256(value) +1);
}
/// @solidity memory-safe-assemblyassembly {
// 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.functiontoHexString(uint256 value, uint256 length) internalpurereturns (stringmemory str) {
str = toHexStringNoPrefix(value, length);
/// @solidity memory-safe-assemblyassembly {
let strLength :=add(mload(str), 2) // Compute the length.mstore(str, 0x3078) // Store the "0x" prefix.
str :=sub(str, 2) // Move the pointer.mstore(str, strLength) // Store 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.functiontoHexStringNoPrefix(uint256 value, uint256 length)
internalpurereturns (stringmemory str)
{
/// @solidity memory-safe-assemblyassembly {
// 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)))
mstore(0x40, add(str, 0x20)) // Allocate the memory.mstore(str, 0) // Zeroize the slot after the string.let end := str // Cache the end to calculate the length later.// 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)
ifiszero(xor(str, start)) { break }
}
if temp {
mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`.revert(0x1c, 0x04)
}
let strLength :=sub(end, str)
str :=sub(str, 0x20)
mstore(str, strLength) // Store the length.
}
}
/// @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.functiontoHexString(uint256 value) internalpurereturns (stringmemory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
let strLength :=add(mload(str), 2) // Compute the length.mstore(str, 0x3078) // Store the "0x" prefix.
str :=sub(str, 2) // Move the pointer.mstore(str, strLength) // Store 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"`.functiontoMinimalHexString(uint256 value) internalpurereturns (stringmemory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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) // Store 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)) // Store 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"`.functiontoMinimalHexStringNoPrefix(uint256 value) internalpurereturns (stringmemory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
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)) // Store 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.functiontoHexStringNoPrefix(uint256 value) internalpurereturns (stringmemory str) {
/// @solidity memory-safe-assemblyassembly {
// 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)
mstore(0x40, add(str, 0x20)) // Allocate the memory.mstore(str, 0) // Zeroize the slot after the string.let end := str // Cache the end to calculate the length later.mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.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)
ifiszero(temp) { break }
}
let strLength :=sub(end, str)
str :=sub(str, 0x20)
mstore(str, strLength) // Store the length.
}
}
/// @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-55functiontoHexStringChecksummed(address value) internalpurereturns (stringmemory str) {
str = toHexString(value);
/// @solidity memory-safe-assemblyassembly {
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)
ifeq(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.functiontoHexString(address value) internalpurereturns (stringmemory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assemblyassembly {
let strLength :=add(mload(str), 2) // Compute the length.mstore(str, 0x3078) // Store the "0x" prefix.
str :=sub(str, 2) // Move the pointer.mstore(str, strLength) // Store the length.
}
}
/// @dev Returns the hexadecimal representation of `value`./// The output is encoded using 2 hexadecimal digits per byte.functiontoHexStringNoPrefix(address value) internalpurereturns (stringmemory str) {
/// @solidity memory-safe-assemblyassembly {
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))
mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.
str :=add(str, 2)
mstore(str, 40) // Store the length.let o :=add(str, 0x20)
mstore(add(o, 40), 0) // Zeroize the slot after the string.
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)
ifeq(i, 20) { break }
}
}
}
/// @dev Returns the hex encoded string from the raw bytes./// The output is encoded using 2 hexadecimal digits per byte.functiontoHexString(bytesmemory raw) internalpurereturns (stringmemory str) {
str = toHexStringNoPrefix(raw);
/// @solidity memory-safe-assemblyassembly {
let strLength :=add(mload(str), 2) // Compute the length.mstore(str, 0x3078) // Store the "0x" prefix.
str :=sub(str, 2) // Move the pointer.mstore(str, strLength) // Store the length.
}
}
/// @dev Returns the hex encoded string from the raw bytes./// The output is encoded using 2 hexadecimal digits per byte.functiontoHexStringNoPrefix(bytesmemory raw) internalpurereturns (stringmemory str) {
/// @solidity memory-safe-assemblyassembly {
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.mstore(0x0f, 0x30313233343536373839616263646566) // Store the "0123456789abcdef" lookup.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.functionruneCount(stringmemory s) internalpurereturns (uint256 result) {
/// @solidity memory-safe-assemblyassembly {
ifmload(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)))))
ifiszero(lt(o, end)) { break }
}
}
}
}
/// @dev Returns if this string is a 7-bit ASCII string./// (i.e. all characters codes are in [0..127])functionis7BitASCII(stringmemory s) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
let mask :=shl(7, div(not(0), 255))
result :=1let n :=mload(s)
if n {
let o :=add(s, 0x20)
let end :=add(o, n)
let last :=mload(end)
mstore(end, 0)
for {} 1 {} {
ifand(mask, mload(o)) {
result :=0break
}
o :=add(o, 0x20)
ifiszero(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`.functionis7BitASCII(stringmemory s, uint128 allowed) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
result :=1ifmload(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)
ifiszero(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.functionto7BitASCIIAllowedLookup(stringmemory s) internalpurereturns (uint128 result) {
/// @solidity memory-safe-assemblyassembly {
ifmload(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)
ifiszero(lt(o, end)) { break }
}
ifshr(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`.functionreplace(stringmemory subject, stringmemory search, stringmemory replacement)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
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)
ifiszero(gt(searchLength, subjectLength)) {
let subjectSearchEnd :=add(sub(subjectEnd, searchLength), 1)
let h :=0ifiszero(lt(searchLength, 0x20)) { h :=keccak256(search, searchLength) }
let m :=shl(3, sub(0x20, and(searchLength, 0x1f)))
let s :=mload(search)
for {} 1 {} {
let t :=mload(subject)
// Whether the first `searchLength % 32` bytes of// `subject` and `search` matches.ifiszero(shr(m, xor(t, s))) {
if h {
ifiszero(eq(keccak256(subject, searchLength), h)) {
mstore(result, t)
result :=add(result, 1)
subject :=add(subject, 1)
ifiszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
// Copy the `replacement` one word at a time.for { let o :=0 } 1 {} {
mstore(add(result, o), mload(add(replacement, o)))
o :=add(o, 0x20)
ifiszero(lt(o, replacementLength)) { break }
}
result :=add(result, replacementLength)
subject :=add(subject, searchLength)
if searchLength {
ifiszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
mstore(result, t)
result :=add(result, 1)
subject :=add(subject, 1)
ifiszero(lt(subject, subjectSearchEnd)) { break }
}
}
let resultRemainder := result
result :=add(mload(0x40), 0x20)
let k :=add(sub(resultRemainder, result), sub(subjectEnd, subject))
// Copy the rest of the string one word at a time.for {} lt(subject, subjectEnd) {} {
mstore(resultRemainder, mload(subject))
resultRemainder :=add(resultRemainder, 0x20)
subject :=add(subject, 0x20)
}
result :=sub(result, 0x20)
let last :=add(add(result, 0x20), k) // Zeroize the slot after the string.mstore(last, 0)
mstore(0x40, add(last, 0x20)) // Allocate the memory.mstore(result, k) // Store the length.
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,/// searching from left to right, starting from `from`./// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.functionindexOf(stringmemory subject, stringmemory search, uint256from)
internalpurereturns (uint256 result)
{
/// @solidity memory-safe-assemblyassembly {
for { let subjectLength :=mload(subject) } 1 {} {
ifiszero(mload(search)) {
ifiszero(gt(from, subjectLength)) {
result := from
break
}
result := subjectLength
break
}
let searchLength :=mload(search)
let subjectStart :=add(subject, 0x20)
result :=not(0) // Initialize to `NOT_FOUND`.
subject :=add(subjectStart, from)
let end :=add(sub(add(subjectStart, subjectLength), searchLength), 1)
let m :=shl(3, sub(0x20, and(searchLength, 0x1f)))
let s :=mload(add(search, 0x20))
ifiszero(and(lt(subject, end), lt(from, subjectLength))) { break }
ifiszero(lt(searchLength, 0x20)) {
for { let h :=keccak256(add(search, 0x20), searchLength) } 1 {} {
ifiszero(shr(m, xor(mload(subject), s))) {
ifeq(keccak256(subject, searchLength), h) {
result :=sub(subject, subjectStart)
break
}
}
subject :=add(subject, 1)
ifiszero(lt(subject, end)) { break }
}
break
}
for {} 1 {} {
ifiszero(shr(m, xor(mload(subject), s))) {
result :=sub(subject, subjectStart)
break
}
subject :=add(subject, 1)
ifiszero(lt(subject, end)) { break }
}
break
}
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,/// searching from left to right./// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.functionindexOf(stringmemory subject, stringmemory search)
internalpurereturns (uint256 result)
{
result = indexOf(subject, search, 0);
}
/// @dev Returns the byte index of the first location of `search` in `subject`,/// searching from right to left, starting from `from`./// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.functionlastIndexOf(stringmemory subject, stringmemory search, uint256from)
internalpurereturns (uint256 result)
{
/// @solidity memory-safe-assemblyassembly {
for {} 1 {} {
result :=not(0) // Initialize to `NOT_FOUND`.let searchLength :=mload(search)
ifgt(searchLength, mload(subject)) { break }
let w := result
let fromMax :=sub(mload(subject), searchLength)
ifiszero(gt(fromMax, from)) { from := fromMax }
let end :=add(add(subject, 0x20), w)
subject :=add(add(subject, 0x20), from)
ifiszero(gt(subject, end)) { break }
// As this function is not too often used,// we shall simply use keccak256 for smaller bytecode size.for { let h :=keccak256(add(search, 0x20), searchLength) } 1 {} {
ifeq(keccak256(subject, searchLength), h) {
result :=sub(subject, add(end, 1))
break
}
subject :=add(subject, w) // `sub(subject, 1)`.ifiszero(gt(subject, end)) { break }
}
break
}
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,/// searching from right to left./// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.functionlastIndexOf(stringmemory subject, stringmemory search)
internalpurereturns (uint256 result)
{
result = lastIndexOf(subject, search, uint256(int256(-1)));
}
/// @dev Returns true if `search` is found in `subject`, false otherwise.functioncontains(stringmemory subject, stringmemory search) internalpurereturns (bool) {
return indexOf(subject, search) != NOT_FOUND;
}
/// @dev Returns whether `subject` starts with `search`.functionstartsWith(stringmemory subject, stringmemory search)
internalpurereturns (bool result)
{
/// @solidity memory-safe-assemblyassembly {
let searchLength :=mload(search)
// Just using keccak256 directly is actually cheaper.// forgefmt: disable-next-item
result :=and(
iszero(gt(searchLength, mload(subject))),
eq(
keccak256(add(subject, 0x20), searchLength),
keccak256(add(search, 0x20), searchLength)
)
)
}
}
/// @dev Returns whether `subject` ends with `search`.functionendsWith(stringmemory subject, stringmemory search)
internalpurereturns (bool result)
{
/// @solidity memory-safe-assemblyassembly {
let searchLength :=mload(search)
let subjectLength :=mload(subject)
// Whether `search` is not longer than `subject`.let withinRange :=iszero(gt(searchLength, subjectLength))
// Just using keccak256 directly is actually cheaper.// forgefmt: disable-next-item
result :=and(
withinRange,
eq(
keccak256(
// `subject + 0x20 + max(subjectLength - searchLength, 0)`.add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))),
searchLength
),
keccak256(add(search, 0x20), searchLength)
)
)
}
}
/// @dev Returns `subject` repeated `times`.functionrepeat(stringmemory subject, uint256 times)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let subjectLength :=mload(subject)
ifiszero(or(iszero(times), iszero(subjectLength))) {
subject :=add(subject, 0x20)
result :=mload(0x40)
let output :=add(result, 0x20)
for {} 1 {} {
// Copy the `subject` one word at a time.for { let o :=0 } 1 {} {
mstore(add(output, o), mload(add(subject, o)))
o :=add(o, 0x20)
ifiszero(lt(o, subjectLength)) { break }
}
output :=add(output, subjectLength)
times :=sub(times, 1)
ifiszero(times) { break }
}
mstore(output, 0) // Zeroize the slot after the string.let resultLength :=sub(output, add(result, 0x20))
mstore(result, resultLength) // Store the length.mstore(0x40, add(result, add(resultLength, 0x40))) // Allocate the memory.
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive)./// `start` and `end` are byte offsets.functionslice(stringmemory subject, uint256 start, uint256 end)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let subjectLength :=mload(subject)
ifiszero(gt(subjectLength, end)) { end := subjectLength }
ifiszero(gt(subjectLength, start)) { start := subjectLength }
iflt(start, end) {
result :=mload(0x40)
let resultLength :=sub(end, start)
mstore(result, resultLength)
subject :=add(subject, start)
let w :=not(0x1f)
// Copy the `subject` one word at a time, backwards.for { let o :=and(add(resultLength, 0x1f), w) } 1 {} {
mstore(add(result, o), mload(add(subject, o)))
o :=add(o, w) // `sub(o, 0x20)`.ifiszero(o) { break }
}
// Zeroize the slot after the string.mstore(add(add(result, 0x20), resultLength), 0)
mstore(0x40, add(result, add(resultLength, 0x40))) // Allocate the memory.
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to the end of the string./// `start` is a byte offset.functionslice(stringmemory subject, uint256 start)
internalpurereturns (stringmemory result)
{
result = slice(subject, start, uint256(int256(-1)));
}
/// @dev Returns all the indices of `search` in `subject`./// The indices are byte offsets.functionindicesOf(stringmemory subject, stringmemory search)
internalpurereturns (uint256[] memory result)
{
/// @solidity memory-safe-assemblyassembly {
let subjectLength :=mload(subject)
let searchLength :=mload(search)
ifiszero(gt(searchLength, subjectLength)) {
subject :=add(subject, 0x20)
search :=add(search, 0x20)
result :=add(mload(0x40), 0x20)
let subjectStart := subject
let subjectSearchEnd :=add(sub(add(subject, subjectLength), searchLength), 1)
let h :=0ifiszero(lt(searchLength, 0x20)) { h :=keccak256(search, searchLength) }
let m :=shl(3, sub(0x20, and(searchLength, 0x1f)))
let s :=mload(search)
for {} 1 {} {
let t :=mload(subject)
// Whether the first `searchLength % 32` bytes of// `subject` and `search` matches.ifiszero(shr(m, xor(t, s))) {
if h {
ifiszero(eq(keccak256(subject, searchLength), h)) {
subject :=add(subject, 1)
ifiszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
// Append to `result`.mstore(result, sub(subject, subjectStart))
result :=add(result, 0x20)
// Advance `subject` by `searchLength`.
subject :=add(subject, searchLength)
if searchLength {
ifiszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
subject :=add(subject, 1)
ifiszero(lt(subject, subjectSearchEnd)) { break }
}
let resultEnd := result
// Assign `result` to the free memory pointer.
result :=mload(0x40)
// Store the length of `result`.mstore(result, shr(5, sub(resultEnd, add(result, 0x20))))
// Allocate memory for result.// We allocate one more word, so this array can be recycled for {split}.mstore(0x40, add(resultEnd, 0x20))
}
}
}
/// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string.functionsplit(stringmemory subject, stringmemory delimiter)
internalpurereturns (string[] memory result)
{
uint256[] memory indices = indicesOf(subject, delimiter);
/// @solidity memory-safe-assemblyassembly {
let w :=not(0x1f)
let indexPtr :=add(indices, 0x20)
let indicesEnd :=add(indexPtr, shl(5, add(mload(indices), 1)))
mstore(add(indicesEnd, w), mload(subject))
mstore(indices, add(mload(indices), 1))
let prevIndex :=0for {} 1 {} {
let index :=mload(indexPtr)
mstore(indexPtr, 0x60)
ifiszero(eq(index, prevIndex)) {
let element :=mload(0x40)
let elementLength :=sub(index, prevIndex)
mstore(element, elementLength)
// Copy the `subject` one word at a time, backwards.for { let o :=and(add(elementLength, 0x1f), w) } 1 {} {
mstore(add(element, o), mload(add(add(subject, prevIndex), o)))
o :=add(o, w) // `sub(o, 0x20)`.ifiszero(o) { break }
}
// Zeroize the slot after the string.mstore(add(add(element, 0x20), elementLength), 0)
// Allocate memory for the length and the bytes,// rounded up to a multiple of 32.mstore(0x40, add(element, and(add(elementLength, 0x3f), w)))
// Store the `element` into the array.mstore(indexPtr, element)
}
prevIndex :=add(index, mload(delimiter))
indexPtr :=add(indexPtr, 0x20)
ifiszero(lt(indexPtr, indicesEnd)) { break }
}
result := indices
ifiszero(mload(delimiter)) {
result :=add(indices, 0x20)
mstore(result, sub(mload(indices), 2))
}
}
}
/// @dev Returns a concatenated string of `a` and `b`./// Cheaper than `string.concat()` and does not de-align the free memory pointer.functionconcat(stringmemory a, stringmemory b)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let w :=not(0x1f)
result :=mload(0x40)
let aLength :=mload(a)
// Copy `a` one word at a time, backwards.for { let o :=and(add(aLength, 0x20), w) } 1 {} {
mstore(add(result, o), mload(add(a, o)))
o :=add(o, w) // `sub(o, 0x20)`.ifiszero(o) { break }
}
let bLength :=mload(b)
let output :=add(result, aLength)
// Copy `b` one word at a time, backwards.for { let o :=and(add(bLength, 0x20), w) } 1 {} {
mstore(add(output, o), mload(add(b, o)))
o :=add(o, w) // `sub(o, 0x20)`.ifiszero(o) { break }
}
let totalLength :=add(aLength, bLength)
let last :=add(add(result, 0x20), totalLength)
mstore(last, 0) // Zeroize the slot after the string.mstore(result, totalLength) // Store the length.mstore(0x40, add(last, 0x20)) // Allocate the memory.
}
}
/// @dev Returns a copy of the string in either lowercase or UPPERCASE./// WARNING! This function is only compatible with 7-bit ASCII strings.functiontoCase(stringmemory subject, bool toUpper)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let length :=mload(subject)
if length {
result :=add(mload(0x40), 0x20)
subject :=add(subject, 1)
let flags :=shl(add(70, shl(5, toUpper)), 0x3ffffff)
let w :=not(0)
for { let o := length } 1 {} {
o :=add(o, w)
let b :=and(0xff, mload(add(subject, o)))
mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20)))
ifiszero(o) { break }
}
result :=mload(0x40)
mstore(result, length) // Store the length.let last :=add(add(result, 0x20), length)
mstore(last, 0) // Zeroize the slot after the string.mstore(0x40, add(last, 0x20)) // Allocate the memory.
}
}
}
/// @dev Returns a string from a small bytes32 string./// `s` must be null-terminated, or behavior will be undefined.functionfromSmallString(bytes32 s) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(0x40)
let n :=0for {} byte(n, s) { n :=add(n, 1) } {} // Scan for '\0'.mstore(result, n) // Store the length.let o :=add(result, 0x20)
mstore(o, s) // Store the bytes of the string.mstore(add(o, n), 0) // Zeroize the slot after the string.mstore(0x40, add(result, 0x40)) // Allocate the memory.
}
}
/// @dev Returns the small string, with all bytes after the first null byte zeroized.functionnormalizeSmallString(bytes32 s) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
for {} byte(result, s) { result :=add(result, 1) } {} // Scan for '\0'.mstore(0x00, s)
mstore(result, 0x00)
result :=mload(0x00)
}
}
/// @dev Returns the string as a normalized null-terminated small string.functiontoSmallString(stringmemory s) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(s)
ifiszero(lt(result, 33)) {
mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`.revert(0x1c, 0x04)
}
result :=shl(shl(3, sub(32, result)), mload(add(s, result)))
}
}
/// @dev Returns a lowercased copy of the string./// WARNING! This function is only compatible with 7-bit ASCII strings.functionlower(stringmemory subject) internalpurereturns (stringmemory result) {
result = toCase(subject, false);
}
/// @dev Returns an UPPERCASED copy of the string./// WARNING! This function is only compatible with 7-bit ASCII strings.functionupper(stringmemory subject) internalpurereturns (stringmemory result) {
result = toCase(subject, true);
}
/// @dev Escapes the string to be used within HTML tags.functionescapeHTML(stringmemory s) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
let end :=add(s, mload(s))
result :=add(mload(0x40), 0x20)
// Store the bytes of the packed offsets and strides into the scratch space.// `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6.mstore(0x1f, 0x900094)
mstore(0x08, 0xc0000000a6ab)
// Store ""&'<>" into the scratch space.mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b))
for {} iszero(eq(s, end)) {} {
s :=add(s, 1)
let c :=and(mload(s), 0xff)
// Not in `["\"","'","&","<",">"]`.ifiszero(and(shl(c, 1), 0x500000c400000000)) {
mstore8(result, c)
result :=add(result, 1)
continue
}
let t :=shr(248, mload(c))
mstore(result, mload(and(t, 0x1f)))
result :=add(result, shr(5, t))
}
let last := result
mstore(last, 0) // Zeroize the slot after the string.
result :=mload(0x40)
mstore(result, sub(last, add(result, 0x20))) // Store the length.mstore(0x40, add(last, 0x20)) // Allocate the memory.
}
}
/// @dev Escapes the string to be used within double-quotes in a JSON./// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes.functionescapeJSON(stringmemory s, bool addDoubleQuotes)
internalpurereturns (stringmemory result)
{
/// @solidity memory-safe-assemblyassembly {
let end :=add(s, mload(s))
result :=add(mload(0x40), 0x20)
if addDoubleQuotes {
mstore8(result, 34)
result :=add(1, result)
}
// Store "\\u0000" in scratch space.// Store "0123456789abcdef" in scratch space.// Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`.// into the scratch space.mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672)
// Bitmask for detecting `["\"","\\"]`.let e :=or(shl(0x22, 1), shl(0x5c, 1))
for {} iszero(eq(s, end)) {} {
s :=add(s, 1)
let c :=and(mload(s), 0xff)
ifiszero(lt(c, 0x20)) {
ifiszero(and(shl(c, 1), e)) {
// Not in `["\"","\\"]`.mstore8(result, c)
result :=add(result, 1)
continue
}
mstore8(result, 0x5c) // "\\".mstore8(add(result, 1), c)
result :=add(result, 2)
continue
}
ifiszero(and(shl(c, 1), 0x3700)) {
// Not in `["\b","\t","\n","\f","\d"]`.mstore8(0x1d, mload(shr(4, c))) // Hex value.mstore8(0x1e, mload(and(c, 15))) // Hex value.mstore(result, mload(0x19)) // "\\u00XX".
result :=add(result, 6)
continue
}
mstore8(result, 0x5c) // "\\".mstore8(add(result, 1), mload(add(c, 8)))
result :=add(result, 2)
}
if addDoubleQuotes {
mstore8(result, 34)
result :=add(1, result)
}
let last := result
mstore(last, 0) // Zeroize the slot after the string.
result :=mload(0x40)
mstore(result, sub(last, add(result, 0x20))) // Store the length.mstore(0x40, add(last, 0x20)) // Allocate the memory.
}
}
/// @dev Escapes the string to be used within double-quotes in a JSON.functionescapeJSON(stringmemory s) internalpurereturns (stringmemory result) {
result = escapeJSON(s, false);
}
/// @dev Returns whether `a` equals `b`.functioneq(stringmemory a, stringmemory b) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
result :=eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b)))
}
}
/// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small string.functioneqs(stringmemory a, bytes32 b) internalpurereturns (bool result) {
/// @solidity memory-safe-assemblyassembly {
// These should be evaluated on compile time, as far as possible.let m :=not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`.let x :=not(or(m, or(b, add(m, and(b, m)))))
let r :=shl(7, iszero(iszero(shr(128, x))))
r :=or(r, shl(6, iszero(iszero(shr(64, shr(r, x))))))
r :=or(r, shl(5, lt(0xffffffff, shr(r, x))))
r :=or(r, shl(4, lt(0xffff, shr(r, x))))
r :=or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
result :=gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))),
xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20)))))
}
}
/// @dev Packs a single string with its length into a single word./// Returns `bytes32(0)` if the length is zero or greater than 31.functionpackOne(stringmemory a) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
// We don't need to zero right pad the string,// since this is our own custom non-standard packing scheme.
result :=mul(
// Load the length and the bytes.mload(add(a, 0x1f)),
// `length != 0 && length < 32`. Abuses underflow.// Assumes that the length is valid and within the block gas limit.lt(sub(mload(a), 1), 0x1f)
)
}
}
/// @dev Unpacks a string packed using {packOne}./// Returns the empty string if `packed` is `bytes32(0)`./// If `packed` is not an output of {packOne}, the output behavior is undefined.functionunpackOne(bytes32 packed) internalpurereturns (stringmemory result) {
/// @solidity memory-safe-assemblyassembly {
result :=mload(0x40) // Grab the free memory pointer.mstore(0x40, add(result, 0x40)) // Allocate 2 words (1 for the length, 1 for the bytes).mstore(result, 0) // Zeroize the length slot.mstore(add(result, 0x1f), packed) // Store the length and bytes.mstore(add(add(result, 0x20), mload(result)), 0) // Right pad with zeroes.
}
}
/// @dev Packs two strings with their lengths into a single word./// Returns `bytes32(0)` if combined length is zero or greater than 30.functionpackTwo(stringmemory a, stringmemory b) internalpurereturns (bytes32 result) {
/// @solidity memory-safe-assemblyassembly {
let aLength :=mload(a)
// We don't need to zero right pad the strings,// since this is our own custom non-standard packing scheme.
result :=mul(
or( // Load the length and the bytes of `a` and `b`.shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))),
mload(sub(add(b, 0x1e), aLength))
),
// `totalLength != 0 && totalLength < 31`. Abuses underflow.// Assumes that the lengths are valid and within the block gas limit.lt(sub(add(aLength, mload(b)), 1), 0x1e)
)
}
}
/// @dev Unpacks strings packed using {packTwo}./// Returns the empty strings if `packed` is `bytes32(0)`./// If `packed` is not an output of {packTwo}, the output behavior is undefined.functionunpackTwo(bytes32 packed)
internalpurereturns (stringmemory resultA, stringmemory resultB)
{
/// @solidity memory-safe-assemblyassembly {
resultA :=mload(0x40) // Grab the free memory pointer.
resultB :=add(resultA, 0x40)
// Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words.mstore(0x40, add(resultB, 0x40))
// Zeroize the length slots.mstore(resultA, 0)
mstore(resultB, 0)
// Store the lengths and bytes.mstore(add(resultA, 0x1f), packed)
mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA))))
// Right pad with zeroes.mstore(add(add(resultA, 0x20), mload(resultA)), 0)
mstore(add(add(resultB, 0x20), mload(resultB)), 0)
}
}
/// @dev Directly returns `a` without copying.functiondirectReturn(stringmemory a) internalpure{
assembly {
// Assumes that the string does not start from the scratch space.let retStart :=sub(a, 0x20)
let retUnpaddedSize :=add(mload(a), 0x40)
// Right pad with zeroes. Just in case the string is produced// by a method that doesn't zero right pad.mstore(add(retStart, retUnpaddedSize), 0)
mstore(retStart, 0x20) // Store the return offset.// End the transaction, returning the string.return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize)))
}
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.4;/// @notice Read and write to persistent storage at a fraction of the cost./// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SSTORE2.sol)/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)/// @author Modified from SSTORE3 (https://github.com/Philogy/sstore3)librarySSTORE2{
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CONSTANTS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev The proxy initialization code.uint256privateconstant _CREATE3_PROXY_INITCODE =0x67363d3d37363d34f03d5260086018f3;
/// @dev Hash of the `_CREATE3_PROXY_INITCODE`./// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.bytes32internalconstant CREATE3_PROXY_INITCODE_HASH =0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* CUSTOM ERRORS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Unable to deploy the storage contract.errorDeploymentFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* WRITE LOGIC *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Writes `data` into the bytecode of a storage contract and returns its address.functionwrite(bytesmemory data) internalreturns (address pointer) {
/// @solidity memory-safe-assemblyassembly {
let n :=mload(data) // Let `l` be `n + 1`. +1 as we prefix a STOP opcode./**
* ---------------------------------------------------+
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------|
* 61 l | PUSH2 l | l | |
* 80 | DUP1 | l l | |
* 60 0xa | PUSH1 0xa | 0xa l l | |
* 3D | RETURNDATASIZE | 0 0xa l l | |
* 39 | CODECOPY | l | [0..l): code |
* 3D | RETURNDATASIZE | 0 l | [0..l): code |
* F3 | RETURN | | [0..l): code |
* 00 | STOP | | |
* ---------------------------------------------------+
* @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
* Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
*/// Do a out-of-gas revert if `n + 1` is more than 2 bytes.mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer :=create(0, add(data, 0x15), add(n, 0xb))
ifiszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract with `salt`/// and returns its normal CREATE2 deterministic address.functionwriteCounterfactual(bytesmemory data, bytes32 salt)
internalreturns (address pointer)
{
/// @solidity memory-safe-assemblyassembly {
let n :=mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer :=create2(0, add(data, 0x15), add(n, 0xb), salt)
ifiszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract and returns its address./// This uses the so-called "CREATE3" workflow,/// which means that `pointer` is agnostic to `data, and only depends on `salt`.functionwriteDeterministic(bytesmemory data, bytes32 salt)
internalreturns (address pointer)
{
/// @solidity memory-safe-assemblyassembly {
let n :=mload(data)
mstore(0x00, _CREATE3_PROXY_INITCODE) // Store the `_PROXY_INITCODE`.let proxy :=create2(0, 0x10, 0x10, salt)
ifiszero(proxy) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.revert(0x1c, 0x04)
}
mstore(0x14, proxy) // Store the proxy's address.// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer :=keccak256(0x1e, 0x17)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
ifiszero(
mul( // The arguments of `mul` are evaluated last to first.extcodesize(pointer),
call(gas(), proxy, 0, add(data, 0x15), add(n, 0xb), codesize(), 0x00)
)
) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* ADDRESS CALCULATIONS *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Returns the initialization code hash of the storage contract for `data`./// Used for mining vanity addresses with create2crunch.functioninitCodeHash(bytesmemory data) internalpurereturns (bytes32 hash) {
/// @solidity memory-safe-assemblyassembly {
let n :=mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.returndatacopy(returndatasize(), returndatasize(), gt(n, 0xfffe))
mstore(data, add(0x61000180600a3d393df300, shl(0x40, n)))
hash :=keccak256(add(data, 0x15), add(n, 0xb))
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Equivalent to `predictCounterfactualAddress(data, salt, address(this))`functionpredictCounterfactualAddress(bytesmemory data, bytes32 salt)
internalviewreturns (address pointer)
{
pointer = predictCounterfactualAddress(data, salt, address(this));
}
/// @dev Returns the CREATE2 address of the storage contract for `data`/// deployed with `salt` by `deployer`./// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.functionpredictCounterfactualAddress(bytesmemory data, bytes32 salt, address deployer)
internalpurereturns (address predicted)
{
bytes32 hash = initCodeHash(data);
/// @solidity memory-safe-assemblyassembly {
// Compute and store the bytecode hash.mstore8(0x00, 0xff) // Write the prefix.mstore(0x35, hash)
mstore(0x01, shl(96, deployer))
mstore(0x15, salt)
predicted :=keccak256(0x00, 0x55)
// Restore the part of the free memory pointer that has been overwritten.mstore(0x35, 0)
}
}
/// @dev Equivalent to `predictDeterministicAddress(salt, address(this))`.functionpredictDeterministicAddress(bytes32 salt) internalviewreturns (address pointer) {
pointer = predictDeterministicAddress(salt, address(this));
}
/// @dev Returns the "CREATE3" deterministic address for `salt` with `deployer`.functionpredictDeterministicAddress(bytes32 salt, address deployer)
internalpurereturns (address pointer)
{
/// @solidity memory-safe-assemblyassembly {
let m :=mload(0x40) // Cache the free memory pointer.mstore(0x00, deployer) // Store `deployer`.mstore8(0x0b, 0xff) // Store the prefix.mstore(0x20, salt) // Store the salt.mstore(0x40, CREATE3_PROXY_INITCODE_HASH) // Store the bytecode hash.mstore(0x14, keccak256(0x0b, 0x55)) // Store the proxy's address.mstore(0x40, m) // Restore the free memory pointer.// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer :=keccak256(0x1e, 0x17)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*//* READ LOGIC *//*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*//// @dev Equivalent to `read(pointer, 0, 2 ** 256 - 1)`.functionread(address pointer) internalviewreturns (bytesmemory data) {
/// @solidity memory-safe-assemblyassembly {
data :=mload(0x40)
let n :=and(sub(extcodesize(pointer), 0x01), 0xffffffffff)
extcodecopy(pointer, add(data, 0x1f), 0x00, add(n, 0x21))
mstore(data, n) // Store the length.mstore(0x40, add(n, add(data, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `read(pointer, start, 2 ** 256 - 1)`.functionread(address pointer, uint256 start) internalviewreturns (bytesmemory data) {
/// @solidity memory-safe-assemblyassembly {
data :=mload(0x40)
let n :=and(sub(extcodesize(pointer), 0x01), 0xffffffffff)
extcodecopy(pointer, add(data, 0x1f), start, add(n, 0x21))
mstore(data, mul(sub(n, start), lt(start, n))) // Store the length.mstore(0x40, add(data, add(0x40, mload(data)))) // Allocate memory.
}
}
/// @dev Returns a slice of the data on `pointer` from `start` to `end`./// `start` and `end` will be clamped to the range `[0, args.length]`./// The `pointer` MUST be deployed via the SSTORE2 write functions./// Otherwise, the behavior is undefined./// Out-of-gas reverts if `pointer` does not have any code.functionread(address pointer, uint256 start, uint256 end)
internalviewreturns (bytesmemory data)
{
/// @solidity memory-safe-assemblyassembly {
data :=mload(0x40)
ifiszero(lt(end, 0xffff)) { end :=0xffff }
let d :=mul(sub(end, start), lt(start, end))
extcodecopy(pointer, add(data, 0x1f), start, add(d, 0x01))
ifiszero(and(0xff, mload(add(data, d)))) {
let n :=sub(extcodesize(pointer), 0x01)
returndatacopy(returndatasize(), returndatasize(), shr(64, n))
d :=mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(data, d) // Store the length.mstore(add(add(data, 0x20), d), 0) // Zeroize the slot after the bytes.mstore(0x40, add(add(data, 0x40), d)) // Allocate memory.
}
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)pragmasolidity ^0.8.0;import"../IERC20.sol";
import"../extensions/IERC20Permit.sol";
import"../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/librarySafeERC20{
usingAddressforaddress;
/**
* @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.
*/functionsafeTransfer(IERC20 token, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, 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.
*/functionsafeTransferFrom(IERC20 token, addressfrom, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/functionsafeApprove(IERC20 token, address spender, uint256 value) internal{
// safeApprove should only be called when setting an initial allowance,// or when resetting it to zero. To increase and decrease it, use// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'require(
(value ==0) || (token.allowance(address(this), spender) ==0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 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.
*/functionsafeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal{
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/functionsafeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal{
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @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.
*/functionforceApprove(IERC20 token, address spender, uint256 value) internal{
bytesmemory approvalCall =abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/functionsafePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal{
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore +1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/function_callOptionalReturn(IERC20 token, bytesmemory data) private{
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that// the target address contains contract code and also asserts for success in the low-level call.bytesmemory returndata =address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length==0||abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/function_callOptionalReturnBool(IERC20 token, bytesmemory data) privatereturns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false// and not revert is the subcall reverts.
(bool success, bytesmemory returndata) =address(token).call(data);
return
success && (returndata.length==0||abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}