S Price: $0.5222 (-2.01%)
    /

    Contract Diff Checker

    Contract Name:
    SolvBTCMultiAssetPool

    Contract Source Code:

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
     * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
     * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
     * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
     *
     * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
     * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
     * case an upgrade adds a module that needs to be initialized.
     *
     * For example:
     *
     * [.hljs-theme-light.nopadding]
     * ```solidity
     * contract MyToken is ERC20Upgradeable {
     *     function initialize() initializer public {
     *         __ERC20_init("MyToken", "MTK");
     *     }
     * }
     *
     * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
     *     function initializeV2() reinitializer(2) public {
     *         __ERC20Permit_init("MyToken");
     *     }
     * }
     * ```
     *
     * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
     * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
     *
     * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
     * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
     *
     * [CAUTION]
     * ====
     * Avoid leaving a contract uninitialized.
     *
     * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
     * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
     * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
     *
     * [.hljs-theme-light.nopadding]
     * ```
     * /// @custom:oz-upgrades-unsafe-allow constructor
     * constructor() {
     *     _disableInitializers();
     * }
     * ```
     * ====
     */
    abstract contract Initializable {
        /**
         * @dev Storage of the initializable contract.
         *
         * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
         * when using with upgradeable contracts.
         *
         * @custom:storage-location erc7201:openzeppelin.storage.Initializable
         */
        struct InitializableStorage {
            /**
             * @dev Indicates that the contract has been initialized.
             */
            uint64 _initialized;
            /**
             * @dev Indicates that the contract is in the process of being initialized.
             */
            bool _initializing;
        }
    
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
    
        /**
         * @dev The contract is already initialized.
         */
        error InvalidInitialization();
    
        /**
         * @dev The contract is not initializing.
         */
        error NotInitializing();
    
        /**
         * @dev Triggered when the contract has been initialized or reinitialized.
         */
        event Initialized(uint64 version);
    
        /**
         * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
         * `onlyInitializing` functions can be used to initialize parent contracts.
         *
         * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
         * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
         * production.
         *
         * Emits an {Initialized} event.
         */
        modifier initializer() {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
    
            // Cache values to avoid duplicated sloads
            bool isTopLevelCall = !$._initializing;
            uint64 initialized = $._initialized;
    
            // Allowed calls:
            // - initialSetup: the contract is not in the initializing state and no previous version was
            //                 initialized
            // - construction: the contract is initialized at version 1 (no reininitialization) and the
            //                 current contract is just being deployed
            bool initialSetup = initialized == 0 && isTopLevelCall;
            bool construction = initialized == 1 && address(this).code.length == 0;
    
            if (!initialSetup && !construction) {
                revert InvalidInitialization();
            }
            $._initialized = 1;
            if (isTopLevelCall) {
                $._initializing = true;
            }
            _;
            if (isTopLevelCall) {
                $._initializing = false;
                emit Initialized(1);
            }
        }
    
        /**
         * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
         * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
         * used to initialize parent contracts.
         *
         * A reinitializer may be used after the original initialization step. This is essential to configure modules that
         * are added through upgrades and that require initialization.
         *
         * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
         * cannot be nested. If one is invoked in the context of another, execution will revert.
         *
         * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
         * a contract, executing them in the right order is up to the developer or operator.
         *
         * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
         *
         * Emits an {Initialized} event.
         */
        modifier reinitializer(uint64 version) {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
    
            if ($._initializing || $._initialized >= version) {
                revert InvalidInitialization();
            }
            $._initialized = version;
            $._initializing = true;
            _;
            $._initializing = false;
            emit Initialized(version);
        }
    
        /**
         * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
         * {initializer} and {reinitializer} modifiers, directly or indirectly.
         */
        modifier onlyInitializing() {
            _checkInitializing();
            _;
        }
    
        /**
         * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
         */
        function _checkInitializing() internal view virtual {
            if (!_isInitializing()) {
                revert NotInitializing();
            }
        }
    
        /**
         * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
         * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
         * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
         * through proxies.
         *
         * Emits an {Initialized} event the first time it is successfully executed.
         */
        function _disableInitializers() internal virtual {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
    
            if ($._initializing) {
                revert InvalidInitialization();
            }
            if ($._initialized != type(uint64).max) {
                $._initialized = type(uint64).max;
                emit Initialized(type(uint64).max);
            }
        }
    
        /**
         * @dev Returns the highest version that has been initialized. See {reinitializer}.
         */
        function _getInitializedVersion() internal view returns (uint64) {
            return _getInitializableStorage()._initialized;
        }
    
        /**
         * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
         */
        function _isInitializing() internal view returns (bool) {
            return _getInitializableStorage()._initializing;
        }
    
        /**
         * @dev Returns a pointer to the storage namespace.
         */
        // solhint-disable-next-line var-name-mixedcase
        function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
            assembly {
                $.slot := INITIALIZABLE_STORAGE
            }
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
    
    pragma solidity ^0.8.20;
    import {Initializable} from "../proxy/utils/Initializable.sol";
    
    /**
     * @dev Contract module that helps prevent reentrant calls to a function.
     *
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
     * available, which can be applied to functions to make sure there are no nested
     * (reentrant) calls to them.
     *
     * Note that because there is a single `nonReentrant` guard, functions marked as
     * `nonReentrant` may not call one another. This can be worked around by making
     * those functions `private`, and then adding `external` `nonReentrant` entry
     * points to them.
     *
     * TIP: If you would like to learn more about reentrancy and alternative ways
     * to protect against it, check out our blog post
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
     */
    abstract contract ReentrancyGuardUpgradeable is Initializable {
        // Booleans are more expensive than uint256 or any type that takes up a full
        // word because each write operation emits an extra SLOAD to first read the
        // slot's contents, replace the bits taken up by the boolean, and then write
        // back. This is the compiler's defense against contract upgrades and
        // pointer aliasing, and it cannot be disabled.
    
        // The values being non-zero value makes deployment a bit more expensive,
        // but in exchange the refund on every call to nonReentrant will be lower in
        // amount. Since refunds are capped to a percentage of the total
        // transaction's gas, it is best to keep them low in cases like this one, to
        // increase the likelihood of the full refund coming into effect.
        uint256 private constant NOT_ENTERED = 1;
        uint256 private constant ENTERED = 2;
    
        /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
        struct ReentrancyGuardStorage {
            uint256 _status;
        }
    
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
    
        function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
            assembly {
                $.slot := ReentrancyGuardStorageLocation
            }
        }
    
        /**
         * @dev Unauthorized reentrant call.
         */
        error ReentrancyGuardReentrantCall();
    
        function __ReentrancyGuard_init() internal onlyInitializing {
            __ReentrancyGuard_init_unchained();
        }
    
        function __ReentrancyGuard_init_unchained() internal onlyInitializing {
            ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
            $._status = NOT_ENTERED;
        }
    
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and making it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            _nonReentrantBefore();
            _;
            _nonReentrantAfter();
        }
    
        function _nonReentrantBefore() private {
            ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
            // On the first call to nonReentrant, _status will be NOT_ENTERED
            if ($._status == ENTERED) {
                revert ReentrancyGuardReentrantCall();
            }
    
            // Any calls to nonReentrant after this point will fail
            $._status = ENTERED;
        }
    
        function _nonReentrantAfter() private {
            ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            $._status = NOT_ENTERED;
        }
    
        /**
         * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
         * `nonReentrant` function in the call stack.
         */
        function _reentrancyGuardEntered() internal view returns (bool) {
            ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
            return $._status == ENTERED;
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
    
    pragma solidity ^0.8.20;
    
    import {IERC20} from "../IERC20.sol";
    
    /**
     * @dev Interface for the optional metadata functions from the ERC20 standard.
     */
    interface IERC20Metadata is IERC20 {
        /**
         * @dev Returns the name of the token.
         */
        function name() external view returns (string memory);
    
        /**
         * @dev Returns the symbol of the token.
         */
        function symbol() external view returns (string memory);
    
        /**
         * @dev Returns the decimals places of the token.
         */
        function decimals() external view returns (uint8);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    
        /**
         * @dev Returns the value of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the value of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves a `value` amount of tokens from the caller's account to `to`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address to, uint256 value) external returns (bool);
    
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
    
        /**
         * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
         * caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 value) external returns (bool);
    
        /**
         * @dev Moves a `value` amount of tokens from `from` to `to` using the
         * allowance mechanism. `value` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address from, address to, uint256 value) external returns (bool);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @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}.
     */
    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[EIP 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);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    
    abstract contract AdminControlUpgradeable is Initializable {
    
        event NewAdmin(address oldAdmin, address newAdmin);
        event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
    
        address public admin;
        address public pendingAdmin;
    
        modifier onlyAdmin() {
            require(msg.sender == admin, "only admin");
            _;
        }
    
        modifier onlyPendingAdmin() {
            require(msg.sender == pendingAdmin, "only pending admin");
            _;
        }
    
        function __AdminControl_init(address admin_) internal onlyInitializing {
            __AdminControl_init_unchained(admin_);
        }
    
        function __AdminControl_init_unchained(address admin_) internal onlyInitializing {
            admin = admin_;
            emit NewAdmin(address(0), admin_);
        }
    
        function transferAdmin(address newPendingAdmin_) external virtual onlyAdmin {
            emit NewPendingAdmin(pendingAdmin, newPendingAdmin_);
            pendingAdmin = newPendingAdmin_;        
        }
    
        function acceptAdmin() external virtual onlyPendingAdmin {
            emit NewAdmin(admin, pendingAdmin);
            admin = pendingAdmin;
            delete pendingAdmin;
        }
    
        uint256[48] private __gap;
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    import "./IERC721.sol";
    
    interface IERC3525 is IERC721 {
        function valueDecimals() external view returns (uint8);
        function balanceOf(uint256 tokenId) external view returns (uint256);
        function slotOf(uint256 tokenId) external view returns (uint256);
        function allowance(uint256 tokenId, address operator) external view returns (uint256);
        
        function approve(address operator, uint256 tokenId) external payable;
        function approve(uint256 tokenId, address operator, uint256 value) external payable;
        function transferFrom(uint256 fromTokenId, uint256 toTokenId, uint256 value) external payable;
        function transferFrom(uint256 fromTokenId, address to, uint256 value) external payable returns (uint256);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    interface IERC3525Receiver {
        function onERC3525Received(address operator, uint256 fromTokenId, uint256 toTokenId, uint256 value, bytes calldata data) external returns (bytes4);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    interface IERC721 {
        function balanceOf(address owner) external view returns (uint256);
        function ownerOf(uint256 tokenId) external view returns (address);
        function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
        function getApproved(uint256 tokenId) external view returns (address);
        function isApprovedForAll(address owner, address operator) external view returns (bool);
    
        function approve(address approved, uint256 tokenId) external payable;
        function setApprovalForAll(address operator, bool approved) external;
        function transferFrom(address from, address to, uint256 tokenId) external payable;
        function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external payable;
        function safeTransferFrom(address from, address to, uint256 tokenId) external payable;
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    interface IERC721Receiver {
        function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
    import "./external/IERC721Receiver.sol";
    import "./external/IERC3525Receiver.sol";
    
    interface ISolvBTC is IERC20, IERC721Receiver, IERC3525Receiver, IERC165 {
    
        error ERC721NotReceivable(address token);
        error ERC3525NotReceivable(address token);
    
        function mint(address account, uint256 value) external;
        function burn(address account, uint256 value) external;
        function burn(uint256 value) external;
    
        function solvBTCMultiAssetPool() external view returns (address);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    interface ISolvBTCMultiAssetPool {
        function deposit(address sft_, uint256 sftId_, uint256 value_) external;
        function withdraw(address sft, uint256 slot, uint256 sftId, uint256 value) external returns (uint256 toSftId_);
    
        function isSftSlotDepositAllowed(address sft_, uint256 slot_) external view returns (bool);
        function isSftSlotWithdrawAllowed(address sft_, uint256 slot_) external view returns (bool);
        function getERC20(address sft_, uint256 slot_) external view returns (address);
        function getHoldingValueSftId(address sft_, uint256 slot_) external view returns (uint256);
        function getSftSlotBalance(address sft_, uint256 slot_) external view returns (uint256);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
    import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
    import "./access/AdminControlUpgradeable.sol";
    import "./utils/ERC3525TransferHelper.sol";
    import "./external/IERC3525.sol";
    import "./ISolvBTCMultiAssetPool.sol";
    import "./ISolvBTC.sol";
    
    contract SolvBTCMultiAssetPool is ISolvBTCMultiAssetPool, ReentrancyGuardUpgradeable, AdminControlUpgradeable {
        struct SftSlotInfo {
            uint256 holdingValueSftId;
            address erc20;
            bool depositAllowed;
            bool withdrawAllowed;
        }
    
        mapping(address => mapping(uint256 => SftSlotInfo)) internal _sftSlotInfos;
    
        event AddSftSlot(address indexed sft, uint256 indexed slot, address indexed erc20, uint256 holdingValueSftId);
        event SftSlotAllowedChanged(address indexed sft, uint256 indexed slot, bool depositAllowed, bool withdrawAllowed);
        event Deposit(
            address indexed owner, address indexed sft, uint256 indexed slot, address erc20, uint256 sftId, uint256 value
        );
        event Withdraw(
            address indexed owner, address indexed sft, uint256 indexed slot, address erc20, uint256 sftId, uint256 value
        );
    
        /// @custom:oz-upgrades-unsafe-allow constructor
        constructor() {
            _disableInitializers();
        }
    
        function initialize() external virtual initializer {
            AdminControlUpgradeable.__AdminControl_init(msg.sender);
            ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
        }
    
        function deposit(address sft_, uint256 sftId_, uint256 value_) external virtual override nonReentrant {
            require(value_ > 0, "SolvBTCMultiAssetPool: deposit amount cannot be 0");
            require(msg.sender == IERC3525(sft_).ownerOf(sftId_), "SolvBTCMultiAssetPool: caller is not sft owner");
    
            uint256 slot = IERC3525(sft_).slotOf(sftId_);
            SftSlotInfo storage sftSlotInfo = _sftSlotInfos[sft_][slot];
            require(sftSlotInfo.depositAllowed, "SolvBTCMultiAssetPool: sft slot deposit not allowed");
    
            uint256 sftBalance = IERC3525(sft_).balanceOf(sftId_);
            if (value_ == sftBalance) {
                ERC3525TransferHelper.doTransferIn(sft_, msg.sender, sftId_);
                if (sftSlotInfo.holdingValueSftId == 0) {
                    sftSlotInfo.holdingValueSftId = sftId_;
                } else {
                    ERC3525TransferHelper.doTransfer(sft_, sftId_, sftSlotInfo.holdingValueSftId, value_);
                    ERC3525TransferHelper.doTransferOut(sft_, 0x000000000000000000000000000000000000dEaD, sftId_);
                }
            } else if (value_ < sftBalance) {
                if (sftSlotInfo.holdingValueSftId == 0) {
                    sftSlotInfo.holdingValueSftId = ERC3525TransferHelper.doTransferIn(sft_, sftId_, value_);
                } else {
                    ERC3525TransferHelper.doTransfer(sft_, sftId_, sftSlotInfo.holdingValueSftId, value_);
                }
            } else {
                revert("SolvBTCMultiAssetPool: deposit amount exceeds sft balance");
            }
    
            ISolvBTC(sftSlotInfo.erc20).mint(msg.sender, value_);
            emit Deposit(msg.sender, sft_, slot, sftSlotInfo.erc20, sftId_, value_);
        }
    
        function withdraw(address sft_, uint256 slot_, uint256 sftId_, uint256 value_)
            external
            virtual
            override
            nonReentrant
            returns (uint256 toSftId_)
        {
            require(value_ > 0, "SolvBTCMultiAssetPool: withdraw amount cannot be 0");
    
            SftSlotInfo storage sftSlotInfo = _sftSlotInfos[sft_][slot_];
            require(sftSlotInfo.withdrawAllowed, "SolvBTCMultiAssetPool: sft slot not allowed");
    
            uint256 sftSlotBalance = getSftSlotBalance(sft_, slot_);
            require(value_ <= sftSlotBalance, "SolvBTCMultiAssetPool: insufficient balance");
    
            ISolvBTC(sftSlotInfo.erc20).burn(msg.sender, value_);
    
            if (sftId_ == 0) {
                toSftId_ = ERC3525TransferHelper.doTransferOut(sft_, sftSlotInfo.holdingValueSftId, msg.sender, value_);
            } else {
                require(slot_ == IERC3525(sft_).slotOf(sftId_), "SolvBTCMultiAssetPool: slot not matched");
                require(msg.sender == IERC3525(sft_).ownerOf(sftId_), "SolvBTCMultiAssetPool: caller is not sft owner");
                ERC3525TransferHelper.doTransfer(sft_, sftSlotInfo.holdingValueSftId, sftId_, value_);
                toSftId_ = sftId_;
            }
    
            emit Withdraw(msg.sender, sft_, slot_, sftSlotInfo.erc20, toSftId_, value_);
        }
    
        function addSftSlotOnlyAdmin(address sft_, uint256 slot_, address erc20_, uint256 holdingValueSftId_)
            external
            virtual
            onlyAdmin
        {
            SftSlotInfo storage sftSlotInfo = _sftSlotInfos[sft_][slot_];
            require(sftSlotInfo.erc20 == address(0), "SolvBTCMultiAssetPool: sft slot already existed");
            require(
                IERC3525(sft_).valueDecimals() == IERC20Metadata(erc20_).decimals(),
                "SolvBTCMultiAssetPool: decimals not matched"
            );
            if (holdingValueSftId_ > 0) {
                require(IERC3525(sft_).slotOf(holdingValueSftId_) == slot_, "SolvBTCMultiAssetPool: slot not matched");
                require(
                    IERC3525(sft_).ownerOf(holdingValueSftId_) == address(this), "SolvBTCMultiAssetPool: sftId not owned"
                );
            }
    
            sftSlotInfo.holdingValueSftId = holdingValueSftId_;
            sftSlotInfo.erc20 = erc20_;
            sftSlotInfo.depositAllowed = true;
            sftSlotInfo.withdrawAllowed = true;
            emit AddSftSlot(sft_, slot_, erc20_, holdingValueSftId_);
        }
    
        function changeSftSlotAllowedOnlyAdmin(address sft_, uint256 slot_, bool depositAllowed_, bool withdrawAllowed_)
            external
            virtual
            onlyAdmin
        {
            SftSlotInfo storage sftSlotInfo = _sftSlotInfos[sft_][slot_];
            require(sftSlotInfo.erc20 != address(0), "SolvBTCMultiAssetPool: sft slot not existed");
    
            sftSlotInfo.depositAllowed = depositAllowed_;
            sftSlotInfo.withdrawAllowed = withdrawAllowed_;
            emit SftSlotAllowedChanged(sft_, slot_, depositAllowed_, withdrawAllowed_);
        }
    
        function isSftSlotDepositAllowed(address sft_, uint256 slot_) public view virtual override returns (bool) {
            return _sftSlotInfos[sft_][slot_].depositAllowed;
        }
    
        function isSftSlotWithdrawAllowed(address sft_, uint256 slot_) public view virtual override returns (bool) {
            return _sftSlotInfos[sft_][slot_].withdrawAllowed;
        }
    
        function getERC20(address sft_, uint256 slot_) public view virtual override returns (address) {
            return _sftSlotInfos[sft_][slot_].erc20;
        }
    
        function getHoldingValueSftId(address sft_, uint256 slot_) public view virtual override returns (uint256) {
            return _sftSlotInfos[sft_][slot_].holdingValueSftId;
        }
    
        function getSftSlotBalance(address sft_, uint256 slot_) public view virtual override returns (uint256) {
            SftSlotInfo storage sftSlotInfo = _sftSlotInfos[sft_][slot_];
            return sftSlotInfo.holdingValueSftId == 0 ? 0 : IERC3525(sft_).balanceOf(sftSlotInfo.holdingValueSftId);
        }
    
        uint256[49] private __gap;
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.20;
    
    interface ERC721Interface {
        function approve(address to, uint256 tokenId) external;
        function transferFrom(address from, address to, uint256 tokenId) external;
        function safeTransferFrom(address from, address to, uint256 tokenId) external;
    }
    
    interface ERC3525Interface {
        function approve(uint256 tokenId, address to, uint256 allowance) external payable;
        function transferFrom(uint256 fromTokenId, uint256 toTokenId, uint256 value) external payable;
        function transferFrom(uint256 fromTokenId, address to, uint256 value) external payable returns (uint256); 
    }
    
    library ERC3525TransferHelper {
        function doApproveId(address underlying, address to, uint256 tokenId) internal {
            ERC721Interface token = ERC721Interface(underlying);
            token.approve(to, tokenId);
        }
    
        function doApproveValue(address underlying, uint256 tokenId, address to, uint256 allowance) internal {
            ERC3525Interface token = ERC3525Interface(underlying);
            token.approve(tokenId, to, allowance);
        }
    
        function doTransferIn(address underlying, address from, uint256 tokenId) internal {
            ERC721Interface token = ERC721Interface(underlying);
            token.transferFrom(from, address(this), tokenId);
        }
        
        function doSafeTransferIn(address underlying, address from, uint256 tokenId) internal {
            ERC721Interface token = ERC721Interface(underlying);
            token.safeTransferFrom(from, address(this), tokenId);
        }
    
        function doSafeTransferOut(address underlying, address to, uint256 tokenId) internal {
            ERC721Interface token = ERC721Interface(underlying);
            token.safeTransferFrom(address(this), to, tokenId);
        }
    
        function doTransferOut(address underlying, address to, uint256 tokenId) internal {
            ERC721Interface token = ERC721Interface(underlying);
            token.transferFrom(address(this), to, tokenId);
        }
    
        function doTransferIn(address underlying, uint256 fromTokenId, uint256 value) internal returns (uint256 newTokenId) {
            ERC3525Interface token = ERC3525Interface(underlying);
            return token.transferFrom(fromTokenId, address(this), value);
        }
    
        function doTransferOut(address underlying, uint256 fromTokenId, address to, uint256 value) internal returns (uint256 newTokenId) {
            ERC3525Interface token = ERC3525Interface(underlying);
            newTokenId = token.transferFrom(fromTokenId, to, value);
        }
    
        function doTransfer(address underlying, address from, address to, uint256 tokenId) internal {
            ERC721Interface token = ERC721Interface(underlying);
            token.transferFrom(from, to, tokenId);
        }
    
        function doTransfer(address underlying, uint256 fromTokenId, uint256 toTokenId, uint256 value) internal {
            ERC3525Interface token = ERC3525Interface(underlying);
            token.transferFrom(fromTokenId, toTokenId, value);
        }
    
    }

    Please enter a contract address above to load the contract details and source code.

    Context size (optional):