Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/policies/deposits/ReceiptTokenManager.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.20;
// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
import {IReceiptTokenManager} from "src/policies/interfaces/deposits/IReceiptTokenManager.sol";
import {IERC165} from "@openzeppelin-5.3.0/interfaces/IERC165.sol";
// Libraries
import {ERC6909Wrappable} from "src/libraries/ERC6909Wrappable.sol";
import {CloneableReceiptToken} from "src/libraries/CloneableReceiptToken.sol";
import {uint2str} from "src/libraries/Uint2Str.sol";
import {String} from "src/libraries/String.sol";
import {IDepositReceiptToken} from "src/interfaces/IDepositReceiptToken.sol";
/// @title ReceiptTokenManager
/// @notice Manager contract for creating and managing ERC6909 receipt tokens for deposits
/// @dev Extracted from DepositManager to reduce contract size.
///
/// Key Features:
/// - Creator-only minting/burning: Only the contract that creates a token can mint/burn it
/// - ERC6909 compatibility with optional ERC20 wrapping via CloneableReceiptToken clones
/// - Deterministic token ID generation based on owner, asset, deposit period, and operator
/// - Automatic wrapped token creation for seamless DeFi integration
///
/// Security Model:
/// - Token ownership is immutable and set to msg.sender during creation
/// - All mint/burn operations are gated by onlyTokenOwner modifier
/// - Token IDs include owner address to prevent collision attacks
contract ReceiptTokenManager is ERC6909Wrappable, IReceiptTokenManager {
using String for string;
// ========== STATE VARIABLES ========== //
/// @notice Maps token ID to the authorized owner (for mint/burn operations)
mapping(uint256 tokenId => address authorizedOwner) internal _tokenOwners;
// ========== CONSTRUCTOR ========== //
constructor() ERC6909Wrappable(address(new CloneableReceiptToken())) {}
// ========== TOKEN CREATION ========== //
/// @inheritdoc IReceiptTokenManager
/// @dev This function reverts if:
/// - The asset is the zero address
/// - The deposit period is 0
/// - The operator is the zero address
/// - A token with the same parameters already exists
function createToken(
IERC20 asset_,
uint8 depositPeriod_,
address operator_,
string memory operatorName_
) external returns (uint256 tokenId) {
// Validate parameters
if (address(asset_) == address(0)) {
revert ReceiptTokenManager_InvalidParams("asset");
}
if (depositPeriod_ == 0) {
revert ReceiptTokenManager_InvalidParams("depositPeriod");
}
if (operator_ == address(0)) {
revert ReceiptTokenManager_InvalidParams("operator");
}
// Use msg.sender as the owner for security
address owner = msg.sender;
// Generate token ID including owner in the hash
tokenId = getReceiptTokenId(owner, asset_, depositPeriod_, operator_);
// Validate token doesn't already exist
if (isValidTokenId(tokenId)) {
revert ReceiptTokenManager_TokenExists(tokenId);
}
// Store the authorized owner for this token
_tokenOwners[tokenId] = owner;
// Create the wrappable token with proper metadata layout for CloneableReceiptToken
string memory tokenName = string
.concat(
operatorName_,
asset_.name(),
" - ",
uint2str(depositPeriod_),
depositPeriod_ == 1 ? " month" : " months"
)
.truncate32();
string memory tokenSymbol = string
.concat(operatorName_, asset_.symbol(), "-", uint2str(depositPeriod_), "m")
.truncate32();
_createWrappableToken(
tokenId,
tokenName,
tokenSymbol,
asset_.decimals(),
abi.encodePacked(
address(this), // Owner at 0x41
address(asset_), // Asset at 0x55
depositPeriod_, // Deposit Period at 0x69
operator_ // Operator at 0x6A
),
true // Automatically create the wrapped token
);
emit TokenCreated(tokenId, owner, address(asset_), depositPeriod_, operator_);
return tokenId;
}
// ========== MINTING/BURNING ========== //
function _onlyTokenOwner(uint256 tokenId_) internal view {
address owner = getTokenOwner(tokenId_);
if (msg.sender != owner) {
revert ReceiptTokenManager_NotOwner(msg.sender, owner);
}
}
modifier onlyTokenOwner(uint256 tokenId_) {
_onlyTokenOwner(tokenId_);
_;
}
/// @inheritdoc IReceiptTokenManager
/// @dev This function reverts if:
/// - The token ID is invalid (not created)
/// - The caller is not the token owner
/// - The recipient is the zero address
/// - The amount is 0
function mint(
address to_,
uint256 tokenId_,
uint256 amount_,
bool shouldWrap_
) external onlyValidTokenId(tokenId_) onlyTokenOwner(tokenId_) {
_mint(to_, tokenId_, amount_, shouldWrap_);
}
/// @inheritdoc IReceiptTokenManager
/// @dev This function reverts if:
/// - The token ID is invalid (not created)
/// - The caller is not the token owner
/// - The account is the zero address
/// - The amount is 0
/// - For wrapped tokens: account has not approved ReceiptTokenManager to spend the wrapped ERC20 token
/// - For unwrapped tokens: account has not approved the caller to spend ERC6909 tokens
/// - The account has insufficient token balance
function burn(
address from_,
uint256 tokenId_,
uint256 amount_,
bool isWrapped_
) external onlyValidTokenId(tokenId_) onlyTokenOwner(tokenId_) {
_burn(from_, tokenId_, amount_, isWrapped_);
}
// ========== VIEW FUNCTIONS ========== //
/// @inheritdoc IReceiptTokenManager
function getReceiptTokenId(
address owner_,
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) public pure override returns (uint256) {
return uint256(keccak256(abi.encode(owner_, asset_, depositPeriod_, operator_)));
}
/// @inheritdoc IReceiptTokenManager
function getTokenName(uint256 tokenId_) public view override returns (string memory) {
return name(tokenId_);
}
/// @inheritdoc IReceiptTokenManager
function getTokenSymbol(uint256 tokenId_) public view override returns (string memory) {
return symbol(tokenId_);
}
/// @inheritdoc IReceiptTokenManager
function getTokenDecimals(uint256 tokenId_) public view override returns (uint8) {
return decimals(tokenId_);
}
/// @inheritdoc IReceiptTokenManager
function getTokenOwner(uint256 tokenId_) public view override returns (address) {
return _tokenOwners[tokenId_];
}
/// @inheritdoc IReceiptTokenManager
function getTokenAsset(uint256 tokenId_) external view override returns (IERC20) {
address wrappedToken = getWrappedToken(tokenId_);
if (wrappedToken == address(0)) return IERC20(address(0));
return IDepositReceiptToken(wrappedToken).asset();
}
/// @inheritdoc IReceiptTokenManager
function getTokenDepositPeriod(uint256 tokenId_) external view override returns (uint8) {
address wrappedToken = getWrappedToken(tokenId_);
if (wrappedToken == address(0)) return 0;
return IDepositReceiptToken(wrappedToken).depositPeriod();
}
/// @inheritdoc IReceiptTokenManager
function getTokenOperator(uint256 tokenId_) external view override returns (address) {
address wrappedToken = getWrappedToken(tokenId_);
if (wrappedToken == address(0)) return address(0);
return IDepositReceiptToken(wrappedToken).operator();
}
// ========== ERC165 ========== //
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC6909Wrappable, IERC165) returns (bool) {
return
interfaceId == type(IReceiptTokenManager).interfaceId ||
ERC6909Wrappable.supportsInterface(interfaceId);
}
}
"
},
"src/interfaces/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
// Imported from forge-std
/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
/// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
event Transfer(address indexed from, address indexed to, uint256 value);
/// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
/// is the new allowance.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @notice Returns the amount of tokens in existence.
function totalSupply() external view returns (uint256);
/// @notice Returns the amount of tokens owned by `account`.
function balanceOf(address account) external view returns (uint256);
/// @notice Moves `amount` tokens from the caller's account to `to`.
function transfer(address to, uint256 amount) external returns (bool);
/// @notice Returns the remaining number of tokens that `spender` is allowed
/// to spend on behalf of `owner`
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
/// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
function approve(address spender, uint256 amount) external returns (bool);
/// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
/// `amount` is then deducted from the caller's allowance.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
/// @notice Returns the name of the token.
function name() external view returns (string memory);
/// @notice Returns the symbol of the token.
function symbol() external view returns (string memory);
/// @notice Returns the decimals places of the token.
function decimals() external view returns (uint8);
}
"
},
"src/policies/interfaces/deposits/IReceiptTokenManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IERC20} from "src/interfaces/IERC20.sol";
import {IERC6909} from "@openzeppelin-5.3.0/interfaces/draft-IERC6909.sol";
import {IERC6909Wrappable} from "src/interfaces/IERC6909Wrappable.sol";
/// @title IReceiptTokenManager
/// @notice Interface for the contract that creates and manages receipt tokens
interface IReceiptTokenManager is IERC6909, IERC6909Wrappable {
// ========== EVENTS ========== //
event TokenCreated(
uint256 indexed tokenId,
address indexed owner,
address indexed asset,
uint8 depositPeriod,
address operator
);
// ========== ERRORS ========== //
error ReceiptTokenManager_TokenExists(uint256 tokenId);
error ReceiptTokenManager_NotOwner(address caller, address owner);
error ReceiptTokenManager_InvalidParams(string reason);
// ========== FUNCTIONS ========== //
/// @notice Creates a new receipt token
/// @dev The caller (msg.sender) becomes the owner of the token for security
///
/// @param asset_ The underlying asset
/// @param depositPeriod_ The deposit period
/// @param operator_ The operator address
/// @param operatorName_ The operator name for token metadata
/// @return tokenId The created token ID
function createToken(
IERC20 asset_,
uint8 depositPeriod_,
address operator_,
string memory operatorName_
) external returns (uint256 tokenId);
/// @notice Mints tokens to a recipient
/// @dev Gated to the owner (creator) of the token
///
/// @param to_ The recipient
/// @param tokenId_ The token ID
/// @param amount_ The amount to mint
/// @param shouldWrap_ Whether to wrap as ERC20
function mint(address to_, uint256 tokenId_, uint256 amount_, bool shouldWrap_) external;
/// @notice Burns tokens from a holder
/// @dev Gated to the owner (creator) of the token
///
/// @param from_ The holder
/// @param tokenId_ The token ID
/// @param amount_ The amount to burn
/// @param isWrapped_ Whether the tokens are wrapped
function burn(address from_, uint256 tokenId_, uint256 amount_, bool isWrapped_) external;
/// @notice Generates a receipt token ID
///
/// @param owner_ The owner address
/// @param asset_ The asset
/// @param depositPeriod_ The deposit period
/// @param operator_ The operator
/// @return tokenId The generated token ID
function getReceiptTokenId(
address owner_,
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) external pure returns (uint256 tokenId);
/// @notice Returns the name of a receipt token
///
/// @param tokenId_ The ID of the receipt token
/// @return name The name of the receipt token
function getTokenName(uint256 tokenId_) external view returns (string memory name);
/// @notice Returns the symbol of a receipt token
///
/// @param tokenId_ The ID of the receipt token
/// @return symbol The symbol of the receipt token
function getTokenSymbol(uint256 tokenId_) external view returns (string memory symbol);
/// @notice Returns the decimals of a receipt token
///
/// @param tokenId_ The ID of the receipt token
/// @return decimals The decimals of the receipt token
function getTokenDecimals(uint256 tokenId_) external view returns (uint8 decimals);
/// @notice Gets the owner of a token
///
/// @param tokenId_ The token ID
/// @return owner The token owner
function getTokenOwner(uint256 tokenId_) external view returns (address owner);
/// @notice Gets the asset of a token
///
/// @param tokenId_ The token ID
/// @return asset The underlying asset
function getTokenAsset(uint256 tokenId_) external view returns (IERC20 asset);
/// @notice Gets the deposit period of a token
///
/// @param tokenId_ The token ID
/// @return depositPeriod The deposit period
function getTokenDepositPeriod(uint256 tokenId_) external view returns (uint8 depositPeriod);
/// @notice Gets the operator of a token
///
/// @param tokenId_ The token ID
/// @return operator The operator address
function getTokenOperator(uint256 tokenId_) external view returns (address operator);
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/interfaces/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
"
},
"src/libraries/ERC6909Wrappable.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.20;
// Interfaces
import {IERC20BurnableMintable} from "src/interfaces/IERC20BurnableMintable.sol";
import {IERC165} from "@openzeppelin-5.3.0/interfaces/IERC165.sol";
import {IERC6909Metadata, IERC6909TokenSupply} from "@openzeppelin-5.3.0/interfaces/draft-IERC6909.sol";
import {IERC6909Wrappable} from "src/interfaces/IERC6909Wrappable.sol";
// Libraries
import {ERC6909} from "@openzeppelin-5.3.0/token/ERC6909/draft-ERC6909.sol";
import {ERC6909Metadata} from "@openzeppelin-5.3.0/token/ERC6909/extensions/draft-ERC6909Metadata.sol";
import {ClonesWithImmutableArgs} from "@clones-with-immutable-args-1.1.2/ClonesWithImmutableArgs.sol";
import {EnumerableSet} from "@openzeppelin-5.3.0/utils/structs/EnumerableSet.sol";
/// @title ERC6909Wrappable
/// @notice This abstract contract extends ERC6909 to allow for wrapping and unwrapping of the token to an ERC20 token.
/// It extends the ERC6909Metadata contract, and additionally implements the IERC6909TokenSupply interface.
abstract contract ERC6909Wrappable is ERC6909Metadata, IERC6909Wrappable, IERC6909TokenSupply {
using ClonesWithImmutableArgs for address;
using EnumerableSet for EnumerableSet.UintSet;
/// @notice The address of the implementation of the ERC20 contract
address private immutable _ERC20_IMPLEMENTATION;
/// @notice The set of all token IDs
EnumerableSet.UintSet internal _wrappableTokenIds;
/// @notice The total supply of each token
mapping(uint256 tokenId => uint256) private _totalSupplies;
/// @notice Additional metadata for each token
mapping(uint256 tokenId => bytes) private _tokenMetadataAdditional;
/// @notice The address of the wrapped ERC20 token for each token
mapping(uint256 tokenId => address) internal _wrappedTokens;
constructor(address erc20Implementation_) {
// Validate that the ERC20 implementation implements the required interface
if (
!IERC165(erc20Implementation_).supportsInterface(
type(IERC20BurnableMintable).interfaceId
)
) revert ERC6909Wrappable_InvalidERC20Implementation(erc20Implementation_);
_ERC20_IMPLEMENTATION = erc20Implementation_;
}
/// @notice Returns the clone initialisation data for a given token ID
///
/// @param tokenId_ The token ID
/// @return tokenData Packed bytes including the name, symbol, decimals and additional metadata
function _getTokenData(uint256 tokenId_) internal view returns (bytes memory tokenData) {
bytes memory additionalMetadata = _tokenMetadataAdditional[tokenId_];
return
abi.encodePacked(
bytes32(bytes(name(tokenId_))),
bytes32(bytes(symbol(tokenId_))),
decimals(tokenId_),
additionalMetadata
);
}
/// @notice Returns the additional metadata for a token ID
///
/// @param tokenId_ The token ID
/// @return additionalData The additional metadata bytes
function _getTokenAdditionalData(
uint256 tokenId_
) internal view returns (bytes memory additionalData) {
return _tokenMetadataAdditional[tokenId_];
}
// ========== MINT/BURN FUNCTIONS ========== //
/// @notice Mints the ERC6909 or ERC20 wrapped token to the recipient
///
/// @param onBehalfOf_ The address to mint the token to
/// @param tokenId_ The ID of the ERC6909 token
/// @param amount_ The amount of tokens to mint
/// @param shouldWrap_ Whether to wrap the token to an ERC20 token
function _mint(
address onBehalfOf_,
uint256 tokenId_,
uint256 amount_,
bool shouldWrap_
) internal onlyValidTokenId(tokenId_) {
if (amount_ == 0) revert ERC6909Wrappable_ZeroAmount();
if (onBehalfOf_ == address(0)) revert ERC6909InvalidReceiver(onBehalfOf_);
if (shouldWrap_) {
_getWrappedToken(tokenId_).mintFor(onBehalfOf_, amount_);
} else {
_mint(onBehalfOf_, tokenId_, amount_);
}
}
/// @notice Burns the ERC6909 or ERC20 wrapped token from the recipient
/// @dev This function reverts if:
/// - amount_ is 0
/// - onBehalfOf_ is 0
/// - onBehalfOf_ is not the caller and has not approved the caller to spend the ERC6909 tokens (note: ERC6909 allowances govern both wrapped and unwrapped token burns)
/// - ERC6909 token handling reverts
///
/// @param onBehalfOf_ The address to burn the token from
/// @param tokenId_ The ID of the ERC6909 token
/// @param amount_ The amount of tokens to burn
/// @param wrapped_ Whether the token is wrapped
function _burn(
address onBehalfOf_,
uint256 tokenId_,
uint256 amount_,
bool wrapped_
) internal onlyValidTokenId(tokenId_) {
if (amount_ == 0) revert ERC6909Wrappable_ZeroAmount();
if (onBehalfOf_ == address(0)) revert ERC6909InvalidSender(onBehalfOf_);
// If the caller is not the owner, check allowance
if (onBehalfOf_ != msg.sender) {
// Spend allowance (since it is not implemented in `ERC6909._burn()` or `CloneableReceiptToken.burnFrom()`)
// The caller is the spender, not this contract
_spendAllowance(onBehalfOf_, msg.sender, tokenId_, amount_);
}
if (wrapped_) {
// Burn the ERC20 token
_getWrappedToken(tokenId_).burnFrom(onBehalfOf_, amount_);
} else {
// Burn the ERC6909 token
_burn(onBehalfOf_, tokenId_, amount_);
}
}
// ========== TOTAL SUPPLY EXTENSION ========== //
/// @inheritdoc IERC6909TokenSupply
function totalSupply(uint256 tokenId_) public view virtual override returns (uint256) {
return _totalSupplies[tokenId_];
}
/// @dev Copied from draft-ERC6909TokenSupply.sol
function _update(
address from,
address to,
uint256 id,
uint256 amount
) internal virtual override {
// Calls ERC6909Metadata._update()
super._update(from, to, id, amount);
if (from == address(0)) {
_totalSupplies[id] += amount;
}
if (to == address(0)) {
unchecked {
// amount <= _balances[from][id] <= _totalSupplies[id]
_totalSupplies[id] -= amount;
}
}
}
// ========== WRAP/UNWRAP FUNCTIONS ========== //
/// @dev Returns the address of the wrapped ERC20 token for a given token ID, or creates a new one if it does not exist
function _getWrappedToken(
uint256 tokenId_
) internal returns (IERC20BurnableMintable wrappedToken) {
// If the wrapped token exists, return it
if (_wrappedTokens[tokenId_] != address(0))
return IERC20BurnableMintable(_wrappedTokens[tokenId_]);
// Validate that the token id exists
bytes memory tokenData = _getTokenData(tokenId_);
if (tokenData.length == 0) revert ERC6909Wrappable_InvalidTokenId(tokenId_);
// Otherwise, create a new wrapped token
wrappedToken = IERC20BurnableMintable(_ERC20_IMPLEMENTATION.clone(tokenData));
_wrappedTokens[tokenId_] = address(wrappedToken);
return wrappedToken;
}
/// @inheritdoc IERC6909Wrappable
function getWrappedToken(uint256 tokenId_) public view returns (address wrappedToken) {
return _wrappedTokens[tokenId_];
}
/// @inheritdoc IERC6909Wrappable
/// @dev This function will burn the ERC6909 token from the caller and mint the wrapped ERC20 token to the same address.
///
/// This function reverts if:
/// - The token ID does not exist
/// - The amount is zero
/// - The caller has an insufficient balance of the token
function wrap(uint256 tokenId_, uint256 amount_) public returns (address wrappedToken) {
// Burn the ERC6909 token
_burn(msg.sender, tokenId_, amount_, false);
// Mint the wrapped ERC20 token to the recipient
IERC20BurnableMintable wrappedToken_ = _getWrappedToken(tokenId_);
wrappedToken_.mintFor(msg.sender, amount_);
// Emit the Wrapped event
emit Wrapped(tokenId_, address(wrappedToken_), msg.sender, amount_);
return address(wrappedToken_);
}
/// @inheritdoc IERC6909Wrappable
/// @dev This function will burn the wrapped ERC20 token from the caller and mint the ERC6909 token to the same address.
///
/// This function reverts if:
/// - The token ID does not exist
/// - The amount is zero
/// - The caller has an insufficient balance of the wrapped token
function unwrap(uint256 tokenId_, uint256 amount_) public {
// Burn the wrapped ERC20 token
_burn(msg.sender, tokenId_, amount_, true);
// Mint the ERC6909 token
_mint(msg.sender, tokenId_, amount_);
// Emit the Unwrapped event
emit Unwrapped(tokenId_, _wrappedTokens[tokenId_], msg.sender, amount_);
}
// ========== TOKEN FUNCTIONS ========== //
/// @inheritdoc IERC6909Wrappable
function isValidTokenId(uint256 tokenId_) public view returns (bool) {
return _wrappableTokenIds.contains(tokenId_);
}
function _onlyValidTokenId(uint256 tokenId_) internal view {
if (!isValidTokenId(tokenId_)) revert ERC6909Wrappable_InvalidTokenId(tokenId_);
}
modifier onlyValidTokenId(uint256 tokenId_) {
_onlyValidTokenId(tokenId_);
_;
}
/// @notice Creates a new wrappable token
/// @dev Reverts if the token ID already exists
function _createWrappableToken(
uint256 tokenId_,
string memory name_,
string memory symbol_,
uint8 decimals_,
bytes memory additionalMetadata_,
bool createWrappedToken_
) internal {
// If the token ID already exists, revert
if (_wrappableTokenIds.contains(tokenId_))
revert ERC6909Wrappable_TokenIdAlreadyExists(tokenId_);
_setName(tokenId_, name_);
_setSymbol(tokenId_, symbol_);
_setDecimals(tokenId_, decimals_);
_tokenMetadataAdditional[tokenId_] = additionalMetadata_;
if (createWrappedToken_) {
_getWrappedToken(tokenId_);
}
// Record the token ID
_wrappableTokenIds.add(tokenId_);
}
/// @inheritdoc IERC6909Wrappable
function getWrappableTokens()
public
view
override
returns (uint256[] memory tokenIds, address[] memory wrappedTokens)
{
tokenIds = _wrappableTokenIds.values();
wrappedTokens = new address[](tokenIds.length);
for (uint256 i; i < tokenIds.length; ++i) {
wrappedTokens[i] = _wrappedTokens[tokenIds[i]];
}
return (tokenIds, wrappedTokens);
}
// ========== ERC165 ========== //
function supportsInterface(
bytes4 interfaceId_
) public view virtual override(ERC6909, IERC165) returns (bool) {
return
interfaceId_ == type(IERC165).interfaceId ||
interfaceId_ == type(IERC6909Wrappable).interfaceId ||
interfaceId_ == type(IERC6909Metadata).interfaceId ||
interfaceId_ == type(IERC6909TokenSupply).interfaceId ||
super.supportsInterface(interfaceId_);
}
}
"
},
"src/libraries/CloneableReceiptToken.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.15;
// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
import {IERC20BurnableMintable} from "src/interfaces/IERC20BurnableMintable.sol";
import {IDepositReceiptToken} from "src/interfaces/IDepositReceiptToken.sol";
import {IERC165} from "@openzeppelin-5.3.0/interfaces/IERC165.sol";
// Libraries
import {CloneERC20} from "src/external/clones/CloneERC20.sol";
/// @title CloneableReceiptToken
/// @notice ERC20 implementation that is deployed as a clone
/// with immutable arguments for each supported input token.
contract CloneableReceiptToken is CloneERC20, IERC20BurnableMintable, IDepositReceiptToken {
// ========== IMMUTABLE ARGS ========== //
// Storage layout:
// 0x00 - name, 32 bytes
// 0x20 - symbol, 32 bytes
// 0x40 - decimals, 1 byte
// 0x41 - owner, 20 bytes
// 0x55 - asset, 20 bytes
// 0x69 - depositPeriod, 1 byte
// 0x6A - operator, 20 bytes
/// @notice The owner of the clone
/// @return _owner The owner address stored in immutable args
function owner() public pure returns (address _owner) {
_owner = _getArgAddress(0x41);
}
/// @notice The underlying asset
/// @return _asset The asset address stored in immutable args
function asset() public pure returns (IERC20 _asset) {
_asset = IERC20(_getArgAddress(0x55));
}
/// @notice The deposit period (in months)
/// @return _depositPeriod The deposit period stored in immutable args
function depositPeriod() public pure returns (uint8 _depositPeriod) {
_depositPeriod = _getArgUint8(0x69);
}
/// @notice The operator that issued the receipt token
/// @return _operator The operator address stored in immutable args
function operator() public pure returns (address _operator) {
_operator = _getArgAddress(0x6A);
}
// ========== OWNER-ONLY FUNCTIONS ========== //
function _onlyOwner() internal view {
if (msg.sender != owner()) revert OnlyOwner();
}
/// @notice Only the owner can call this function
modifier onlyOwner() {
_onlyOwner();
_;
}
/// @notice Mint tokens to the specified address
/// @dev This is owner-only, as the underlying token is custodied by the owner.
/// Minting should be performed through the owner contract.
///
/// @param to_ The address to mint tokens to
/// @param amount_ The amount of tokens to mint
function mintFor(address to_, uint256 amount_) external onlyOwner {
_mint(to_, amount_);
}
/// @notice Burn tokens from the specified address
/// @dev This is gated to the owner, as burning is controlled.
/// Burning should be performed through the owner contract.
/// The owner is expected to handle spending approval before calling this function.
/// This function does NOT check or update allowances.
///
/// @param from_ The address to burn tokens from
/// @param amount_ The amount of tokens to burn
function burnFrom(address from_, uint256 amount_) external onlyOwner {
_burn(from_, amount_);
}
// ========== ERC165 ========== //
function supportsInterface(bytes4 interfaceId_) public pure returns (bool) {
// super does not implement ERC165, so no need to call it
return
interfaceId_ == type(IERC165).interfaceId ||
interfaceId_ == type(IERC20).interfaceId ||
interfaceId_ == type(IERC20BurnableMintable).interfaceId ||
interfaceId_ == type(IDepositReceiptToken).interfaceId;
}
}
"
},
"src/libraries/Uint2Str.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8;
// Some fancy math to convert a uint into a string, courtesy of Provable Things.
// Updated to work with solc 0.8.0.
// https://github.com/provable-things/ethereum-api/blob/master/provableAPI_0.6.sol
function uint2str(uint256 _i) pure returns (string memory) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint256 k = len;
while (_i != 0) {
k = k - 1;
bstr[k] = bytes1(uint8(48 + (_i % 10)));
_i /= 10;
}
return string(bstr);
}
"
},
"src/libraries/String.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
library String {
error EndBeforeStartIndex(uint256 startIndex, uint256 endIndex);
error EndIndexOutOfBounds(uint256 endIndex, uint256 length);
/// @notice Truncates a string to 32 bytes
function truncate32(string memory str_) internal pure returns (string memory) {
return string(abi.encodePacked(bytes32(abi.encodePacked(str_))));
}
/// @notice Returns a substring of a string
///
/// @param str_ The string to get the substring of
/// @param startIndex_ The index to start the substring at
/// @param endIndex_ The index to end the substring at
/// @return resultString The substring
function substring(
string memory str_,
uint256 startIndex_,
uint256 endIndex_
) internal pure returns (string memory) {
bytes memory strBytes = bytes(str_);
if (endIndex_ < startIndex_) revert EndBeforeStartIndex(startIndex_, endIndex_);
if (endIndex_ > strBytes.length) revert EndIndexOutOfBounds(endIndex_, strBytes.length);
bytes memory result = new bytes(endIndex_ - startIndex_);
for (uint256 i = startIndex_; i < endIndex_; i++) {
result[i - startIndex_] = strBytes[i];
}
return string(result);
}
/// @notice Returns a substring of a string from a given index
///
/// @param str_ The string to get the substring of
/// @param startIndex_ The index to start the substring at
/// @return resultString The substring
function substringFrom(
string memory str_,
uint256 startIndex_
) internal pure returns (string memory) {
return substring(str_, startIndex_, bytes(str_).length);
}
}
"
},
"src/interfaces/IDepositReceiptToken.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
import {IERC20} from "src/interfaces/IERC20.sol";
/// @title IDepositReceiptToken
/// @notice Interface for a deposit receipt token
/// @dev This interface adds additional metadata to the IERC20 interface that is necessary for deposit receipt tokens.
interface IDepositReceiptToken is IERC20 {
// ========== ERRORS ========== //
error OnlyOwner();
// ========== VIEW FUNCTIONS ========== //
function owner() external view returns (address _owner);
function asset() external view returns (IERC20 _asset);
function depositPeriod() external view returns (uint8 _depositPeriod);
function operator() external view returns (address _operator);
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/interfaces/draft-IERC6909.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/draft-IERC6909.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-6909 compliant contract, as defined in the
* https://eips.ethereum.org/EIPS/eip-6909[ERC].
*/
interface IERC6909 is IERC165 {
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set for a token of type `id`.
* The new allowance is `amount`.
*/
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
/**
* @dev Emitted when `owner` grants or revokes operator status for a `spender`.
*/
event OperatorSet(address indexed owner, address indexed spender, bool approved);
/**
* @dev Emitted when `amount` tokens of type `id` are moved from `sender` to `receiver` initiated by `caller`.
*/
event Transfer(
address caller,
address indexed sender,
address indexed receiver,
uint256 indexed id,
uint256 amount
);
/**
* @dev Returns the amount of tokens of type `id` owned by `owner`.
*/
function balanceOf(address owner, uint256 id) external view returns (uint256);
/**
* @dev Returns the amount of tokens of type `id` that `spender` is allowed to spend on behalf of `owner`.
*
* NOTE: Does not include operator allowances.
*/
function allowance(address owner, address spender, uint256 id) external view returns (uint256);
/**
* @dev Returns true if `spender` is set as an operator for `owner`.
*/
function isOperator(address owner, address spender) external view returns (bool);
/**
* @dev Sets an approval to `spender` for `amount` of tokens of type `id` from the caller's tokens. An `amount` of
* `type(uint256).max` signifies an unlimited approval.
*
* Must return true.
*/
function approve(address spender, uint256 id, uint256 amount) external returns (bool);
/**
* @dev Grants or revokes unlimited transfer permission of any token id to `spender` for the caller's tokens.
*
* Must return true.
*/
function setOperator(address spender, bool approved) external returns (bool);
/**
* @dev Transfers `amount` of token type `id` from the caller's account to `receiver`.
*
* Must return true.
*/
function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);
/**
* @dev Transfers `amount` of token type `id` from `sender` to `receiver`.
*
* Must return true.
*/
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);
}
/**
* @dev Optional extension of {IERC6909} that adds metadata functions.
*/
interface IERC6909Metadata is IERC6909 {
/**
* @dev Returns the name of the token of type `id`.
*/
function name(uint256 id) external view returns (string memory);
/**
* @dev Returns the ticker symbol of the token of type `id`.
*/
function symbol(uint256 id) external view returns (string memory);
/**
* @dev Returns the number of decimals for the token of type `id`.
*/
function decimals(uint256 id) external view returns (uint8);
}
/**
* @dev Optional extension of {IERC6909} that adds content URI functions.
*/
interface IERC6909ContentURI is IERC6909 {
/**
* @dev Returns URI for the contract.
*/
function contractURI() external view returns (string memory);
/**
* @dev Returns the URI for the token of type `id`.
*/
function tokenURI(uint256 id) external view returns (string memory);
}
/**
* @dev Optional extension of {IERC6909} that adds a token supply function.
*/
interface IERC6909TokenSupply is IERC6909 {
/**
* @dev Returns the total supply of the token of type `id`.
*/
function totalSupply(uint256 id) external view returns (uint256);
}
"
},
"src/interfaces/IERC6909Wrappable.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @title IERC6909Wrappable
/// @notice Declares interface for an ERC6909 implementation that allows for wrapping and unwrapping ERC6909 tokens to and from ERC20 tokens
interface IERC6909Wrappable {
// ========== EVENTS ========== //
event Wrapped(
uint256 indexed tokenId,
address indexed wrappedToken,
address indexed account,
uint256 amount
);
event Unwrapped(
uint256 indexed tokenId,
address indexed wrappedToken,
address indexed account,
uint256 amount
);
// ========== ERRORS ========== //
error ERC6909Wrappable_TokenIdAlreadyExists(uint256 tokenId);
error ERC6909Wrappable_InvalidTokenId(uint256 tokenId);
error ERC6909Wrappable_InvalidERC20Implementation(address erc20Implementation);
error ERC6909Wrappable_ZeroAmount();
// ========== WRAP/UNWRAP FUNCTIONS ========== //
/// @notice Wraps an ERC6909 token to an ERC20 token
///
/// @param tokenId_ The ID of the ERC6909 token
/// @param amount_ The amount of tokens to wrap
/// @return wrappedToken The address of the wrapped ERC20 token
function wrap(uint256 tokenId_, uint256 amount_) external returns (address wrappedToken);
/// @notice Unwraps an ERC20 token to an ERC6909 token
///
/// @param tokenId_ The ID of the ERC6909 token
/// @param amount_ The amount of tokens to unwrap
function unwrap(uint256 tokenId_, uint256 amount_) external;
/// @notice Returns the address of the wrapped ERC20 token for a given token ID
///
/// @param tokenId_ The ID of the ERC6909 token
/// @return wrappedToken The address of the wrapped ERC20 token (or zero address)
function getWrappedToken(uint256 tokenId_) external view returns (address wrappedToken);
// ========== TOKEN FUNCTIONS ========== //
/// @notice Returns whether a token ID is valid
///
/// @param tokenId_ The ID of the ERC6909 token
/// @return isValid Whether the token ID is valid
function isValidTokenId(uint256 tokenId_) external view returns (bool isValid);
/// @notice Returns the token IDs and wrapped token addresses of all tokens
///
/// @return tokenIds The IDs of all tokens
/// @return wrappedTokens The wrapped token addresses of all tokens
function getWrappableTokens()
external
view
returns (uint256[] memory tokenIds, address[] memory wrappedTokens);
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @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[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
},
"src/interfaces/IERC20BurnableMintable.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
import {IERC20} from "src/interfaces/IERC20.sol";
interface IERC20BurnableMintable is IERC20 {
/// @notice Mints tokens to the specified address
///
/// @param to_ The address to mint tokens to
/// @param amount_ The amount of tokens to mint
function mintFor(address to_, uint256 amount_) external;
/// @notice Burns tokens from the specified address
///
/// @param from_ The address to burn tokens from
/// @param amount_ The amount of tokens to burn
function burnFrom(address from_, uint256 amount_) external;
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/token/ERC6909/draft-ERC6909.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC6909/draft-ERC6909.sol)
pragma solidity ^0.8.20;
import {IERC6909} from "../../interfaces/draft-IERC6909.sol";
import {Context} from "../../utils/Context.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of ERC-6909.
* See https://eips.ethereum.org/EIPS/eip-6909
*/
contract ERC6909 is Context, ERC165, IERC6909 {
mapping(address owner => mapping(uint256 id => uint256)) private _balances;
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
mapping(address owner => mapping(address spender => mapping(uint256 id => uint256))) private _allowances;
error ERC6909InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 id);
error ERC6909InsufficientAllowance(address spender, uint256 allowance, uint256 needed, uint256 id);
error ERC6909InvalidApprover(address approver);
error ERC6909InvalidReceiver(address receiver);
error ERC6909InvalidSender(address sender);
error ERC6909InvalidSpender(address spender);
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC6909).interfaceId || super.supportsInterface(interfaceId);
}
/// @inheritdoc IERC6909
function balanceOf(address owner, uint256 id) public view virtual override returns (uint256) {
return _balances[owner][id];
}
/// @inheritdoc IERC6909
function allowance(address owner, address spender, uint256 id) public view virtual override returns (uint256) {
return _allowances[owner][spender][id];
}
/// @inheritdoc IERC6909
function isOperator(address owner, address spender) public view virtual override returns (bool) {
return _operatorApprovals[owner][spender];
}
/// @inheritdoc IERC6909
function approve(address spender, uint256 id, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, id, amount);
return true;
}
/// @inheritdoc IERC6909
function setOperator(address spender, bool approved) public virtual override returns (bool) {
_setOperator(_msgSender(), spender, approved);
return true;
}
/// @inheritdoc IERC6909
function transfer(address receiver, uint256 id, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), receiver, id, amount);
return true;
}
/// @inheritdoc IERC6909
function transferFrom(
address sender,
address receiver,
uint256 id,
uint256 amount
) public virtual override returns (bool) {
address caller = _msgSender();
if (sender != caller && !isOperator(sender, caller)) {
_spendAllowance(sender, caller, id, amount);
}
_transfer(sender, receiver, id, amount);
return true;
}
/**
* @dev Creates `amount` of token `id` and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address to, uint256 id, uint256 amount) internal {
if (to == address(0)) {
revert ERC6909InvalidReceiver(address(0));
}
_update(address(0), to, id, amount);
}
/**
* @dev Moves `amount` of token `id` from `from` to `to` without checking for approvals. This function verifies
* that neither the sender nor the receiver are address(0), which means it cannot mint or burn tokens.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 id, uint256 amount) internal {
if (from == address(0)) {
revert ERC6909InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC6909InvalidReceiver(address(0));
}
_update(from, to, id, amount);
}
/**
* @dev Destroys a `amount` of token `id` from `account`.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address from, uint256 id, uint256 amount) internal {
if (from == address(0)) {
revert ERC6909InvalidSender(address(0));
}
_update(from, address(0), id, amount);
}
/**
* @dev Transfers `amount` of token `id` from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 id, uint256 amount) internal virtual {
address caller = _msgSender();
if (from != address(0)) {
uint256 fromBalance = _balances[from][id];
if (fromBalance < amount) {
revert ERC6909InsufficientBalance(from, fromBalance, amount, id);
}
unchecked {
// Overflow not possible: amount <= fromBalance.
_balances[from][id] = fromBalance - amount;
}
}
if (to != address(0)) {
_balances[to][id] += amount;
}
emit Transfer(caller, from, to, id, amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner`'s `id` tokens.
*
* This internal function is equivalent to `approve`, and can be used to e.g. set automatic allowances for certain
* subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 id, uint256 amount) internal virtual {
if (owner == address(0)) {
revert ERC6909InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC6909InvalidSpender(address(0));
}
_allowances[owner][spender][id] = amount;
emit Approval(owner, spender, id, amount);
}
/**
* @dev Approve `spender` to operate on all of `owner`'s tokens
*
* This internal function is equivalent to `setOperator`, and can be used to e.g. set automatic allowances for
* certain subsystems, etc.
*
* Emits an {OperatorSet} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _setOperator(address owner, address spender, bool approved) internal virtual {
if (owner == address(0)) {
revert ERC6909InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC6909InvalidSpender(address(0));
}
_operatorApprovals[owner][spender] = approved;
emit OperatorSet(owner, spender, approved);
}
/**
* @dev Updates `owner`'s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 id, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender, id);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < amount) {
revert ERC6909InsufficientAllowance(spender, currentAllowance, amount, id);
}
unchecked {
_allowances[owner][spender][id] = currentAllowance - amount;
}
}
}
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/token/ERC6909/extensions/draft-ERC6909Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC6909/extensions/draft-ERC6909Metadata.sol)
pragma solidity ^0.8.20;
import {ERC6909} from "../draft-ERC6909.sol";
import {IERC6909Metadata} from "../../../interfaces/draft-IERC6909.sol";
/**
* @dev Implementation of the Metadata extension defined in ERC6909. Exposes the name, symbol, and decimals of each token id.
*/
contract ERC6909Metadata is ERC6909, IERC6909Metadata {
struct TokenMetadata {
string name;
string symbol;
uint8 decimals;
}
mapping(uint256 id => TokenMetadata) private _tokenMetadata;
/// @dev The name of the token of type `id` was updated to `newName`.
event ERC6909NameUpdated(uint256 indexed id, string newName);
/// @dev The symbol for the token of type `id` was updated to `newSymbol`.
event ERC6909SymbolUpdated(uint256 indexed id, string newSymbol);
/// @dev The decimals value for token of type `id` was updated to `newDecimals`.
event ERC6909DecimalsUpdated(uint256 indexed id, uint8 newDecimals);
/// @inheritdoc IERC6909Metadata
function name(uint256 id) public view virtual override returns (string memory) {
return _tokenMetadata[id].name;
}
/// @inheritdoc IERC6909Metadata
function symbol(uint256 id) public view virtual override returns (string memory) {
return _tokenMetadata[id].symbol;
}
/// @inheritdoc IERC6909Metadata
function decimals(uint256 id) public view virtual override returns (uint8) {
return _tokenMetadata[id].decimals;
}
/**
* @dev Sets the `name` for a given token of type `id`.
*
* Emits an {ERC6909NameUpdated} event.
*/
function _setName(uint256 id, string memory newName) internal virtual {
_tokenMetadata[id].name = newName;
emit ERC6909NameUpdated(id, newName);
}
/**
* @dev Sets the `symbol` for a given token of type `id`.
*
* Emits an {ERC6909SymbolUpdated} event.
*/
function _setSymbol(uint256 id, string memory newSymbol) internal virtual {
_tokenMetadata[id].symbol = newSymbol;
emit ERC6909SymbolUpdated(id, newSymbol);
}
/**
* @dev Sets the `decimals` for a given token of type `id`.
*
* Emits an {ERC6909DecimalsUpdated} event.
*/
function _setDecimals(uint256 id, uint8 newDecimals) internal virtual {
_tokenMetadata[id].decimals = newDecimals;
emit ERC6909DecimalsUpdated(id, newDecimals);
}
}
"
},
"dependencies/clones-with-immutable-args-1.1.2/src/ClonesWithImmutableArgs.sol": {
"content": "// SPDX-License-Identifier: BSD
pragma solidity ^0.8.4;
/// @title ClonesWithImmutableArgs
/// @author wighawag, zefram.eth, nick.eth
/// @notice Enables creating clone contracts with immutable args
library ClonesWithImmutableArgs {
/// @dev The CREATE3 proxy bytecode.
uint256 private constant _CREATE3_PROXY_BYTECODE =
0x67363d3d37363d34f03d5260086018f3;
/// @dev Hash of the `_CREATE3_PROXY_BYTECODE`.
/// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
bytes32 private constant _CREATE3_PROXY_BYTECODE_HASH =
0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
error CreateFail();
error InitializeFail();
enum CloneType {
CREATE,
CREATE2,
PREDICT_CREATE2
}
/// @notice Creates a clone proxy of the implementation contract, with immutable args
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return instance The address of the created clone
function clone(
address implementation,
bytes memory data
) internal returns (address payable instance) {
return clone(implementation, data, 0);
}
/// @notice Creates a clone proxy of the implementation contract, with immutable args
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @param value The amount of wei to transfer to the created clone
/// @return instance The address of the created clone
function clone(
address implementation,
bytes memory data,
uint256 value
) internal returns (address payable instance) {
bytes memory creationcode = getCreationBytecode(implementation, data);
// solhint-disable-next-line no-inline-assembly
assembly {
instance := create(
value,
add(creationcode, 0x20),
mload(creationcode)
)
}
if (instance == address(0)) {
revert CreateFail();
}
}
/// @notice Creates a clone proxy of the implementation contract, with immutable args,
/// using CREATE2
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return instance The address of the created clone
function clone2(
address implementation,
bytes memory data
) internal returns (address payable instance) {
return clone2(implementation, data, 0);
}
/// @notice Creates a clone proxy of the implementation contract, with immutable args,
/// using CREATE2
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @param value The amount of wei to transfer to the created clone
/// @return instance The address of the created clone
function clone2(
address implementation,
bytes memory data,
uint256 value
) internal returns (address payable instance) {
bytes memory creationcode = getCreationBytecode(implementation, data);
// solhint-disable-next-line no-inline-assembly
assembly {
instance := create2(
value,
add(creationcode, 0x20),
mload(creationcode),
0
)
}
if (instance == address(0)) {
revert CreateFail();
}
}
/// @notice Computes the address of a clone created using CREATE2
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return instance The address of the clone
function addressOfClone2(
address implementation,
bytes memory data
) internal view returns (address payable instance) {
bytes memory creationcode = getCreationBytecode(implementation, data);
bytes32 bytecodeHash = keccak256(creationcode);
instance = payable(
address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
bytes32(0),
bytecodeHash
)
)
)
)
)
);
}
/// @notice Computes bytecode for a clone
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return ret Creation bytecode for the clone contract
function getCreationBytecode(
address implementation,
bytes memory data
) internal pure returns (bytes memory ret) {
// unrealistic for memory ptr or data length to exceed 256 bits
unchecked {
uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call
uint256 creationSize = 0x41 + extraLength;
uint256 runSize = creationSize - 10;
uint256 dataPtr;
uint256 ptr;
// solhint-disable-next-line no-inline-assembly
assembly {
ret := mload(0x40)
mstore(ret, creationSize)
mstore(0x40, add(ret, creationSize))
ptr := add(ret, 0x20)
// -------------------------------------------------------------------------------------------------------------
// CREATION (10 bytes)
// -------------------------------------------------------------------------------------------------------------
// 61 runtime | PUSH2 runtime (r) | r | –
mstore(
ptr,
0x6100000000000000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x01), shl(240, runSize)) // size of the contract running bytecode (16 bits)
// creation size = 0a
// 3d | RETURNDATASIZE | 0 r | –
// 81 | DUP2 | r 0 r | –
// 60 creation | PUSH1 creation (c) | c r 0 r | –
// 3d | RETURNDATASIZE | 0 c r 0 r | –
// 39 | CODECOPY | 0 r | [0-runSize): runtime code
// f3 | RETURN | | [0-runSize): runtime code
// -------------------------------------------------------------------------------------------------------------
// RUNTIME (55 bytes + extraLength)
// -------------------------------------------------------------------------------------------------------------
// 3d | RETURNDATASIZE | 0 | –
// 3d | RETURNDATASIZE | 0 0 | –
// 3d | RETURNDATASIZE | 0 0 0 | –
// 3d | RETURNDATASIZE | 0 0 0 0 | –
// 36 | CALLDATASIZE | cds 0 0 0 0 | –
// 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | –
// 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | –
// 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata
// 61 | PUSH2 extra | extra 0 0 0 0 | [0, cds) = calldata
mstore(
add(ptr, 0x03),
0x3d81600a3d39f33d3d3d3d363d3d376100000000000000000000000000000000
)
mstore(add(ptr, 0x13), shl(240, extraLength))
// 60 0x37 | PUSH1 0x37 | 0x37 extra 0 0 0 0 | [0, cds) = calldata // 0x37 (55) is runtime size - data
// 36 | CALLDATASIZE | cds 0x37 extra 0 0 0 0 | [0, cds) = calldata
// 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData
// 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData
// 61 extra | PUSH2 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData
mstore(
add(ptr, 0x15),
0x6037363936610000000000000000000000000000000000000000000000000000
)
mstor
Submitted on: 2025-11-07 16:14:14
Comments
Log in to comment.
No comments yet.