P2pMorphoProxyFactory

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/adapters/morpho/p2pMorphoProxyFactory/P2pMorphoProxyFactory.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import "../../../common/IMorphoBundler.sol";
import "../../../p2pYieldProxyFactory/P2pYieldProxyFactory.sol";
import "./IP2pMorphoProxyFactory.sol";
import "../p2pMorphoProxy/P2pMorphoProxy.sol";

error P2pMorphoProxyFactory__DistributorNotTrusted(address _distributor);
error P2pMorphoProxyFactory__ZeroTrustedDistributorAddress();

contract P2pMorphoProxyFactory is P2pYieldProxyFactory, IP2pMorphoProxyFactory {
    IMorphoBundler private immutable i_morphoBundler;

    mapping(address => bool) private s_trustedDistributors;

    constructor(
        address _p2pSigner,
        address _p2pTreasury,
        address _allowedCalldataChecker,
        address _morphoBundler
    ) P2pYieldProxyFactory(_p2pSigner) {
        i_morphoBundler = IMorphoBundler(_morphoBundler);
        i_referenceP2pYieldProxy =
            new P2pMorphoProxy(address(this), _p2pTreasury, _allowedCalldataChecker, _morphoBundler);
    }

    /// @inheritdoc IP2pMorphoProxyFactory
    function setTrustedDistributor(address _newTrustedDistributor) external override onlyP2pOperator {
        require(_newTrustedDistributor != address(0), P2pMorphoProxyFactory__ZeroTrustedDistributorAddress());
        s_trustedDistributors[_newTrustedDistributor] = true;
        emit P2pMorphoProxyFactory__TrustedDistributorSet(_newTrustedDistributor);
    }

    /// @inheritdoc IP2pMorphoProxyFactory
    function removeTrustedDistributor(address _trustedDistributor) external override onlyP2pOperator {
        s_trustedDistributors[_trustedDistributor] = false;
        emit P2pMorphoProxyFactory__TrustedDistributorRemoved(_trustedDistributor);
    }

    /// @inheritdoc IP2pMorphoProxyFactory
    function checkMorphoUrdClaim(address _p2pOperatorToCheck, bool _shouldCheckP2pOperator, address _distributor)
        external
        view
        override
    {
        if (_shouldCheckP2pOperator) {
            require(getP2pOperator() == _p2pOperatorToCheck, P2pOperator__UnauthorizedAccount(_p2pOperatorToCheck));
        }
        require(s_trustedDistributors[_distributor], P2pMorphoProxyFactory__DistributorNotTrusted(_distributor));
    }

    /// @inheritdoc IP2pMorphoProxyFactory
    function isTrustedDistributor(address _distributor) external view override returns (bool) {
        return s_trustedDistributors[_distributor];
    }

    function getP2pOperator() public view override(P2pYieldProxyFactory, IP2pYieldProxyFactory) returns (address) {
        return super.getP2pOperator();
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override(P2pYieldProxyFactory, IERC165)
        returns (bool)
    {
        return interfaceId == type(IP2pMorphoProxyFactory).interfaceId || super.supportsInterface(interfaceId);
    }
}
"
    },
    "src/common/IMorphoBundler.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

/// @title IMorphoBundler
/// @notice Based on https://github.com/morpho-org/morpho-blue-bundlers
interface IMorphoBundler {
    function erc4626Deposit(address vault, uint256 assets, uint256 minShares, address receiver) external payable;

    function erc4626Redeem(address vault, uint256 shares, uint256 minAssets, address receiver, address owner)
        external
        payable;

    function urdClaim(
        address distributor,
        address account,
        address reward,
        uint256 amount,
        bytes32[] calldata proof,
        bool skipRevert
    ) external payable;

    function multicall(bytes[] memory data) external payable;
}
"
    },
    "src/p2pYieldProxyFactory/P2pYieldProxyFactory.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import "../@openzeppelin/contracts/proxy/Clones.sol";
import "../@openzeppelin/contracts/utils/Address.sol";
import "../@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import "../@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../access/P2pOperator2Step.sol";
import "../common/AllowedCalldataChecker.sol";
import "../p2pYieldProxy/P2pYieldProxy.sol";
import "./IP2pYieldProxyFactory.sol";

/// @dev Error when the P2pSigner address is zero
error P2pYieldProxyFactory__ZeroP2pSignerAddress();

/// @dev Error when the P2pSigner signature is invalid
error P2pYieldProxyFactory__InvalidP2pSignerSignature();

/// @dev Error when the P2pSigner signature is expired
error P2pYieldProxyFactory__P2pSignerSignatureExpired(uint256 _p2pSignerSigDeadline);

/// @dev Error when no rules are defined
error P2pYieldProxyFactory__NoRulesDefined(address _target, bytes4 _selector);

/// @dev Error when no calldata is allowed
error P2pYieldProxyFactory__NoCalldataAllowed(address _target, bytes4 _selector);

/// @dev Error when the calldata is too short for the start with rule
error P2pYieldProxyFactory__CalldataTooShortForStartsWithRule(
    uint256 _calldataAfterSelectorLength, uint32 _ruleIndex, uint32 _bytesCount
);

/// @dev Error when the calldata starts with rule is violated
error P2pYieldProxyFactory__CalldataStartsWithRuleViolated(bytes _actual, bytes _expected);

/// @dev Error when the calldata is too short for the ends with rule
error P2pYieldProxyFactory__CalldataTooShortForEndsWithRule(uint256 _calldataAfterSelectorLength, uint32 _bytesCount);

/// @dev Error when the calldata ends with rule is violated
error P2pYieldProxyFactory__CalldataEndsWithRuleViolated(bytes _actual, bytes _expected);

/// @title P2pYieldProxyFactory
/// @author P2P Validator <info@p2p.org>
/// @notice P2pYieldProxyFactory is a factory contract for creating P2pYieldProxy contracts
abstract contract P2pYieldProxyFactory is AllowedCalldataChecker, P2pOperator2Step, ERC165, IP2pYieldProxyFactory {
    using SignatureChecker for address;
    using ECDSA for bytes32;

    /// @notice Reference P2pYieldProxy contract
    P2pYieldProxy internal immutable i_referenceP2pYieldProxy;

    /// @notice P2pSigner address
    address internal s_p2pSigner;

    /// @notice All proxies
    address[] internal s_allProxies;

    /// @notice Modifier to check if the P2pSigner signature should not expire
    modifier p2pSignerSignatureShouldNotExpire(uint256 _p2pSignerSigDeadline) {
        require(
            block.timestamp < _p2pSignerSigDeadline,
            P2pYieldProxyFactory__P2pSignerSignatureExpired(_p2pSignerSigDeadline)
        );
        _;
    }

    /// @notice Modifier to check if the P2pSigner signature should be valid
    modifier p2pSignerSignatureShouldBeValid(
        uint96 _clientBasisPoints,
        uint256 _p2pSignerSigDeadline,
        bytes calldata _p2pSignerSignature
    ) {
        require(
            s_p2pSigner.isValidSignatureNow(
                getHashForP2pSigner(msg.sender, _clientBasisPoints, _p2pSignerSigDeadline).toEthSignedMessageHash(),
                _p2pSignerSignature
            ),
            P2pYieldProxyFactory__InvalidP2pSignerSignature()
        );
        _;
    }

    /// @notice Constructor for P2pYieldProxyFactory
    /// @param _p2pSigner The P2pSigner address
    constructor(address _p2pSigner) P2pOperator(msg.sender) {
        _transferP2pSigner(_p2pSigner);
    }

    /// @inheritdoc IP2pYieldProxyFactory
    function transferP2pSigner(address _newP2pSigner) external override onlyP2pOperator {
        _transferP2pSigner(_newP2pSigner);
    }

    /// @inheritdoc IP2pYieldProxyFactory
    function deposit(
        address _vault,
        uint256 _amount,
        uint96 _clientBasisPoints,
        uint256 _p2pSignerSigDeadline,
        bytes calldata _p2pSignerSignature
    )
        external
        override
        p2pSignerSignatureShouldNotExpire(_p2pSignerSigDeadline)
        p2pSignerSignatureShouldBeValid(_clientBasisPoints, _p2pSignerSigDeadline, _p2pSignerSignature)
        returns (address p2pYieldProxyAddress)
    {
        // create proxy if not created yet
        P2pYieldProxy p2pYieldProxy = _getOrCreateP2pYieldProxy(_clientBasisPoints);

        // deposit via proxy
        p2pYieldProxy.deposit(_vault, _amount);

        emit P2pYieldProxyFactory__Deposited(msg.sender, _clientBasisPoints);

        p2pYieldProxyAddress = address(p2pYieldProxy);
    }

    function _transferP2pSigner(address _newP2pSigner) private {
        require(_newP2pSigner != address(0), P2pYieldProxyFactory__ZeroP2pSignerAddress());
        emit P2pYieldProxyFactory__P2pSignerTransferred(s_p2pSigner, _newP2pSigner);
        s_p2pSigner = _newP2pSigner;
    }

    /// @notice Creates a new P2pYieldProxy contract instance if not created yet
    function _getOrCreateP2pYieldProxy(uint96 _clientBasisPoints) private returns (P2pYieldProxy p2pYieldProxy) {
        address p2pYieldProxyAddress = predictP2pYieldProxyAddress(msg.sender, _clientBasisPoints);
        uint256 codeSize = p2pYieldProxyAddress.code.length;
        if (codeSize > 0) {
            return P2pYieldProxy(p2pYieldProxyAddress);
        }

        p2pYieldProxy = P2pYieldProxy(
            Clones.cloneDeterministic(address(i_referenceP2pYieldProxy), _getSalt(msg.sender, _clientBasisPoints))
        );

        p2pYieldProxy.initialize(msg.sender, _clientBasisPoints);

        s_allProxies.push(address(p2pYieldProxy));

        emit P2pYieldProxyFactory__ProxyCreated(address(p2pYieldProxy), msg.sender, _clientBasisPoints);
    }

    /// @notice Calculates the salt required for deterministic clone creation
    /// depending on client address and client basis points
    /// @param _clientAddress address
    /// @param _clientBasisPoints basis points (10000 = 100%)
    /// @return bytes32 salt
    function _getSalt(address _clientAddress, uint96 _clientBasisPoints) private pure returns (bytes32) {
        return keccak256(abi.encode(_clientAddress, _clientBasisPoints));
    }

    /// @inheritdoc IP2pYieldProxyFactory
    function predictP2pYieldProxyAddress(address _client, uint96 _clientBasisPoints)
        public
        view
        override
        returns (address)
    {
        return
            Clones.predictDeterministicAddress(address(i_referenceP2pYieldProxy), _getSalt(_client, _clientBasisPoints));
    }

    /// @inheritdoc IP2pYieldProxyFactory
    function getReferenceP2pYieldProxy() external view override returns (address) {
        return address(i_referenceP2pYieldProxy);
    }

    /// @inheritdoc IP2pYieldProxyFactory
    function getHashForP2pSigner(address _client, uint96 _clientBasisPoints, uint256 _p2pSignerSigDeadline)
        public
        view
        override
        returns (bytes32)
    {
        return keccak256(abi.encode(_client, _clientBasisPoints, _p2pSignerSigDeadline, address(this), block.chainid));
    }

    /// @inheritdoc IP2pYieldProxyFactory
    function getP2pSigner() external view override returns (address) {
        return s_p2pSigner;
    }

    function getP2pOperator() public view virtual override(IP2pYieldProxyFactory, P2pOperator) returns (address) {
        return super.getP2pOperator();
    }

    /// @inheritdoc IP2pYieldProxyFactory
    function getAllProxies() external view override returns (address[] memory) {
        return s_allProxies;
    }

    /// @inheritdoc ERC165
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IP2pYieldProxyFactory).interfaceId || super.supportsInterface(interfaceId);
    }
}
"
    },
    "src/adapters/morpho/p2pMorphoProxyFactory/IP2pMorphoProxyFactory.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import "../../../p2pYieldProxyFactory/IP2pYieldProxyFactory.sol";

interface IP2pMorphoProxyFactory is IP2pYieldProxyFactory {
    /// @notice Emitted when a distributor is marked as trusted
    event P2pMorphoProxyFactory__TrustedDistributorSet(address indexed _newTrustedDistributor);
    /// @notice Emitted when a distributor loses the trusted status
    event P2pMorphoProxyFactory__TrustedDistributorRemoved(address indexed _trustedDistributor);

    /// @notice Marks a distributor as trusted for URD claims
    /// @param _newTrustedDistributor The distributor address to trust
    function setTrustedDistributor(address _newTrustedDistributor) external;

    /// @notice Removes a distributor from the trusted list
    /// @param _trustedDistributor The distributor address to remove
    function removeTrustedDistributor(address _trustedDistributor) external;

    /// @notice Validates whether a URD claim can be executed
    /// @param _p2pOperatorToCheck The operator whose permissions should be verified
    /// @param _shouldCheckP2pOperator Indicates if operator verification is required
    /// @param _distributor The distributor address involved in the claim
    function checkMorphoUrdClaim(address _p2pOperatorToCheck, bool _shouldCheckP2pOperator, address _distributor)
        external
        view;

    /// @notice Returns whether a distributor is trusted
    /// @param _distributor The distributor address to query
    /// @return isTrusted True if the distributor is trusted, false otherwise
    function isTrustedDistributor(address _distributor) external view returns (bool);
}
"
    },
    "src/adapters/morpho/p2pMorphoProxy/P2pMorphoProxy.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import "../../../p2pYieldProxy/P2pYieldProxy.sol";
import "../../../common/IMorphoBundler.sol";
import "../../../@openzeppelin/contracts/interfaces/IERC4626.sol";
import "../p2pMorphoProxyFactory/IP2pMorphoProxyFactory.sol";
import "./IP2pMorphoProxy.sol";

error P2pMorphoProxy__NothingClaimed();
error P2pMorphoProxy__NotP2pOperator(address _caller);
error P2pMorphoProxy__ZeroAccruedRewards();
error P2pMorphoProxy__ZeroVaultAddress();
error P2pMorphoProxy__VaultAssetNotSet(address _vault);

contract P2pMorphoProxy is P2pYieldProxy, IP2pMorphoProxy {
    using SafeERC20 for IERC20;

    IMorphoBundler private immutable i_morphoBundler;

    modifier onlyP2pOperator() {
        address p2pOperator = i_factory.getP2pOperator();
        require(msg.sender == p2pOperator, P2pMorphoProxy__NotP2pOperator(msg.sender));
        _;
    }

    constructor(
        address _factory,
        address _p2pTreasury,
        address _allowedCalldataChecker,
        address _morphoBundler
    ) P2pYieldProxy(_factory, _p2pTreasury, _allowedCalldataChecker) {
        i_morphoBundler = IMorphoBundler(_morphoBundler);
    }

    /// @inheritdoc IP2pMorphoProxy
    function deposit(address _vault, uint256 _amount) external override(IP2pMorphoProxy, P2pYieldProxy) {
        require(_vault != address(0), P2pMorphoProxy__ZeroVaultAddress());

        address asset = IERC4626(_vault).asset();
        require(asset != address(0), P2pMorphoProxy__VaultAssetNotSet(_vault));

        uint256 minShares = IERC4626(_vault).convertToShares(_amount);
        bytes[] memory dataForMulticall = new bytes[](1);
        dataForMulticall[0] =
            abi.encodeCall(IMorphoBundler.erc4626Deposit, (_vault, _amount, minShares, address(this)));
        bytes memory depositCalldata = abi.encodeCall(IMorphoBundler.multicall, (dataForMulticall));
        _deposit(_vault, address(i_morphoBundler), depositCalldata, asset, _amount, true);
    }

    /// @inheritdoc IP2pMorphoProxy
    function withdraw(address _vault, uint256 _shares) external override onlyClient {
        require(_vault != address(0), P2pMorphoProxy__ZeroVaultAddress());

        address asset = IERC4626(_vault).asset();
        require(asset != address(0), P2pMorphoProxy__VaultAssetNotSet(_vault));

        uint256 minAssets = IERC4626(_vault).convertToAssets(_shares);
        bytes[] memory dataForMulticall = new bytes[](1);
        dataForMulticall[0] = abi.encodeCall(
            IMorphoBundler.erc4626Redeem, (_vault, _shares, minAssets, address(this), address(this))
        );
        bytes memory redeemCalldata = abi.encodeCall(IMorphoBundler.multicall, (dataForMulticall));
        _withdraw(_vault, asset, address(i_morphoBundler), redeemCalldata, _shares);
    }

    /// @inheritdoc IP2pMorphoProxy
    function withdrawAccruedRewards(address _vault) external override onlyP2pOperator {
        require(_vault != address(0), P2pMorphoProxy__ZeroVaultAddress());

        address asset = IERC4626(_vault).asset();
        require(asset != address(0), P2pMorphoProxy__VaultAssetNotSet(_vault));

        int256 amount = calculateAccruedRewards(_vault, asset);
        require(amount > 0, P2pMorphoProxy__ZeroAccruedRewards());

        uint256 shares = IERC4626(_vault).convertToShares(uint256(amount));
        uint256 minAssets = IERC4626(_vault).convertToAssets(shares);
        bytes[] memory dataForMulticall = new bytes[](1);
        dataForMulticall[0] = abi.encodeCall(
            IMorphoBundler.erc4626Redeem, (_vault, shares, minAssets, address(this), address(this))
        );
        bytes memory redeemCalldata = abi.encodeCall(IMorphoBundler.multicall, (dataForMulticall));
        _withdraw(_vault, asset, address(i_morphoBundler), redeemCalldata, shares);
    }

    /// @inheritdoc IP2pMorphoProxy
    function morphoUrdClaim(address _distributor, address _reward, uint256 _amount, bytes32[] calldata _proof)
        external
        override
        nonReentrant
    {
        bool shouldCheckP2pOperator;
        if (msg.sender != s_client) {
            shouldCheckP2pOperator = true;
        }
        IP2pMorphoProxyFactory(address(i_factory)).checkMorphoUrdClaim(msg.sender, shouldCheckP2pOperator, _distributor);

        bytes memory urdClaimCalldata =
            abi.encodeCall(IMorphoBundler.urdClaim, (_distributor, address(this), _reward, _amount, _proof, false));
        bytes[] memory dataForMulticall = new bytes[](1);
        dataForMulticall[0] = urdClaimCalldata;

        uint256 assetAmountBefore = IERC20(_reward).balanceOf(address(this));
        i_morphoBundler.multicall(dataForMulticall);
        uint256 assetAmountAfter = IERC20(_reward).balanceOf(address(this));

        uint256 newAssetAmount = assetAmountAfter - assetAmountBefore;
        require(newAssetAmount > 0, P2pMorphoProxy__NothingClaimed());

        uint256 p2pAmount = (newAssetAmount * (10_000 - s_clientBasisPoints)) / 10_000;
        uint256 clientAmount = newAssetAmount - p2pAmount;

        if (p2pAmount > 0) {
            IERC20(_reward).safeTransfer(i_p2pTreasury, p2pAmount);
        }
        IERC20(_reward).safeTransfer(s_client, clientAmount);

        emit P2pMorphoProxy__ClaimedMorphoUrd(_distributor, _reward, newAssetAmount, p2pAmount, clientAmount);
    }

    /// @inheritdoc IP2pYieldProxy
    function calculateAccruedRewards(address _vault, address _asset)
        public
        view
        override(IP2pYieldProxy, P2pYieldProxy)
        returns (int256)
    {
        uint256 shares = IERC20(_vault).balanceOf(address(this));
        uint256 currentAmount = IERC4626(_vault).convertToAssets(shares);
        uint256 userPrincipal = getUserPrincipal(_asset);
        return int256(currentAmount) - int256(userPrincipal);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override(P2pYieldProxy, IERC165)
        returns (bool)
    {
        return interfaceId == type(IP2pMorphoProxy).interfaceId || super.supportsInterface(interfaceId);
    }

}
"
    },
    "src/@openzeppelin/contracts/proxy/Clones.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/Clones.sol)

pragma solidity 0.8.30;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 *
 * _Available since v3.4._
 */
library Clones {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            instance := create(0, ptr, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            instance := create2(0, ptr, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(address implementation, bytes32 salt, address deployer)
        internal
        pure
        returns (address predicted)
    {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000)
            mstore(add(ptr, 0x38), shl(0x60, deployer))
            mstore(add(ptr, 0x4c), salt)
            mstore(add(ptr, 0x6c), keccak256(ptr, 0x37))
            predicted := keccak256(add(ptr, 0x37), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(address implementation, bytes32 salt)
        internal
        view
        returns (address predicted)
    {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}
"
    },
    "src/@openzeppelin/contracts/utils/Address.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)

pragma solidity 0.8.30;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @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
     * ====
     *
     * [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.
     * ====
     */
    function isContract(address account) internal view returns (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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable 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._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "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._
     */
    function functionCall(address target, bytes memory data, string memory errorMessage)
        internal
        returns (bytes memory)
    {
        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._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        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._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage)
        internal
        returns (bytes memory)
    {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        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._
     */
    function functionStaticCall(address target, bytes memory data, string memory errorMessage)
        internal
        view
        returns (bytes memory)
    {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        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._
     */
    function functionDelegateCall(address target, bytes memory data, string memory errorMessage)
        internal
        returns (bytes memory)
    {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(bool success, bytes memory returndata, string memory errorMessage)
        internal
        pure
        returns (bytes memory)
    {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                assembly ("memory-safe") {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}
"
    },
    "src/@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/SignatureChecker.sol)

pragma solidity 0.8.30;

import "./ECDSA.sol";
import "../../interfaces/IERC1271.sol";

/**
 * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA
 * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like
 * Argent and Gnosis Safe.
 *
 * _Available since v4.1._
 */
library SignatureChecker {
    /**
     * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
     * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`.
     *
     * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
     * change through time. It could return true at block N and false at block N+1 (or the opposite).
     */
    function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
        (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature);
        return (error == ECDSA.RecoverError.NoError && recovered == signer)
            || isValidERC1271SignatureNow(signer, hash, signature);
    }

    /**
     * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
     * against the signer smart contract using ERC1271.
     *
     * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
     * change through time. It could return true at block N and false at block N+1 (or the opposite).
     */
    function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
        internal
        view
        returns (bool)
    {
        (bool success, bytes memory result) =
            signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature));
        return (
            success && result.length >= 32
                && abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)
        );
    }
}
"
    },
    "src/@openzeppelin/contracts/utils/introspection/ERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity 0.8.30;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}
"
    },
    "src/access/P2pOperator2Step.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

// Copy and rename of OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity 0.8.30;

import {P2pOperator} from "./P2pOperator.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (a P2pOperator) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {P2pOperator.sol} contract includes a two-step mechanism to transfer
 * P2pOperator, where the new P2pOperator must call {acceptP2pOperator} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of P2pOperator to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * The initial P2pOperator is specified at deployment time in the constructor for `P2pOperator.sol`. This
 * can later be changed with {transferP2pOperator} and {acceptP2pOperator}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (P2pOperator.sol).
 */
abstract contract P2pOperator2Step is P2pOperator {
    address private s_pendingP2pOperator;

    event P2pOperator2Step__P2pOperatorTransferStarted(
        address indexed _previousP2pOperator, address indexed _newP2pOperator
    );

    /**
     * @dev Returns the address of the pending P2pOperator.
     */
    function getPendingP2pOperator() public view virtual returns (address) {
        return s_pendingP2pOperator;
    }

    /**
     * @dev Starts the P2pOperator transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current P2pOperator.
     *
     * Setting `_newP2pOperator` to the zero address is allowed; this can be used to cancel an initiated P2pOperator transfer.
     */
    function transferP2pOperator(address _newP2pOperator) public virtual override onlyP2pOperator {
        s_pendingP2pOperator = _newP2pOperator;
        emit P2pOperator2Step__P2pOperatorTransferStarted(getP2pOperator(), _newP2pOperator);
    }

    /**
     * @dev Transfers P2pOperator of the contract to a new account (`_newP2pOperator`) and deletes any pending P2pOperator.
     * Internal function without access restriction.
     */
    function _transferP2pOperator(address _newP2pOperator) internal virtual override {
        delete s_pendingP2pOperator;
        super._transferP2pOperator(_newP2pOperator);
    }

    /**
     * @dev The new P2pOperator accepts the P2pOperator transfer.
     */
    function acceptP2pOperator() public virtual {
        address sender = msg.sender;
        if (s_pendingP2pOperator != sender) {
            revert P2pOperator__UnauthorizedAccount(sender);
        }
        _transferP2pOperator(sender);
    }
}
"
    },
    "src/common/AllowedCalldataChecker.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import "../@openzeppelin/contracts-upgradable/proxy/utils/Initializable.sol";
import "./IAllowedCalldataChecker.sol";

/// @dev No extra calls are allowed for now. AllowedCalldataChecker can be upgraded in the future.
error AllowedCalldataChecker__NoAllowedCalldata();

/// @title AllowedCalldataChecker
/// @author P2P Validator <info@p2p.org>
/// @notice Upgradable contract for checking if a calldata is allowed
contract AllowedCalldataChecker is IAllowedCalldataChecker, Initializable {
    function initialize() public initializer {
        // do nothing in this implementation
    }

    /// @inheritdoc IAllowedCalldataChecker
    function checkCalldata(address, bytes4, bytes calldata) public pure override {
        revert AllowedCalldataChecker__NoAllowedCalldata();
    }
}
"
    },
    "src/p2pYieldProxy/P2pYieldProxy.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import "../@openzeppelin/contracts-upgradable/security/ReentrancyGuardUpgradeable.sol";
import "../@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../@openzeppelin/contracts/utils/Address.sol";
import "../@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "../common/AllowedCalldataChecker.sol";
import "../p2pYieldProxyFactory/IP2pYieldProxyFactory.sol";
import "../structs/P2pStructs.sol";
import "./IP2pYieldProxy.sol";

error P2pYieldProxy__ZeroAddressAsset();
error P2pYieldProxy__ZeroAssetAmount();
error P2pYieldProxy__ZeroSharesAmount();
error P2pYieldProxy__InvalidClientBasisPoints(uint96 _clientBasisPoints);
error P2pYieldProxy__NotFactory(address _factory);
error P2pYieldProxy__DifferentActuallyDepositedAmount(uint256 _requestedAmount, uint256 _actualAmount);
error P2pYieldProxy__NotFactoryCalled(address _msgSender, IP2pYieldProxyFactory _actualFactory);
error P2pYieldProxy__NotClientCalled(address _msgSender, address _actualClient);
error P2pYieldProxy__ZeroAddressFactory();
error P2pYieldProxy__ZeroAddressP2pTreasury();
error P2pYieldProxy__ZeroAddressYieldProtocolAddress();
error P2pYieldProxy__ZeroAllowedCalldataChecker();
error P2pYieldProxy__DataTooShort();

/// @title P2pYieldProxy
/// @notice P2pYieldProxy is a contract that allows a client to deposit and withdraw assets from a yield protocol.
abstract contract P2pYieldProxy is Initializable, ReentrancyGuardUpgradeable, ERC165, IP2pYieldProxy {
    using SafeERC20 for IERC20;
    using Address for address;

    /// @dev P2pYieldProxyFactory
    IP2pYieldProxyFactory internal immutable i_factory;

    /// @dev P2pTreasury
    address internal immutable i_p2pTreasury;

    IAllowedCalldataChecker internal immutable i_allowedCalldataChecker;

    /// @dev Client
    address internal s_client;

    /// @dev Client basis points
    uint96 internal s_clientBasisPoints;

    // asset => amount
    mapping(address => uint256) internal s_totalDeposited;

    // asset => amount
    mapping(address => Withdrawn) internal s_totalWithdrawn;

    /// @notice If caller is not factory, revert
    modifier onlyFactory() {
        if (msg.sender != address(i_factory)) {
            revert P2pYieldProxy__NotFactoryCalled(msg.sender, i_factory);
        }
        _;
    }

    /// @notice If caller is not client, revert
    modifier onlyClient() {
        if (msg.sender != s_client) {
            revert P2pYieldProxy__NotClientCalled(msg.sender, s_client);
        }
        _;
    }

    /// @dev Modifier for checking if a calldata is allowed
    /// @param _yieldProtocolAddress The address of the yield protocol
    /// @param _yieldProtocolCalldata The calldata (encoded signature + arguments) to be passed to the yield protocol
    modifier calldataShouldBeAllowed(address _yieldProtocolAddress, bytes calldata _yieldProtocolCalldata) {
        // validate yieldProtocolCalldata for yieldProtocolAddress
        bytes4 selector = _getFunctionSelector(_yieldProtocolCalldata);
        i_allowedCalldataChecker.checkCalldata(_yieldProtocolAddress, selector, _yieldProtocolCalldata[4:]);
        _;
    }

    /// @notice Constructor for P2pYieldProxy
    /// @param _factory The factory address
    /// @param _p2pTreasury The P2pTreasury address
    /// @param _allowedCalldataChecker AllowedCalldataChecker
    constructor(address _factory, address _p2pTreasury, address _allowedCalldataChecker) {
        require(_factory != address(0), P2pYieldProxy__ZeroAddressFactory());
        i_factory = IP2pYieldProxyFactory(_factory);

        require(_p2pTreasury != address(0), P2pYieldProxy__ZeroAddressP2pTreasury());
        i_p2pTreasury = _p2pTreasury;

        require(_allowedCalldataChecker != address(0), P2pYieldProxy__ZeroAllowedCalldataChecker());
        i_allowedCalldataChecker = IAllowedCalldataChecker(_allowedCalldataChecker);
    }

    /// @inheritdoc IP2pYieldProxy
    function initialize(address _client, uint96 _clientBasisPoints) external override initializer onlyFactory {
        __ReentrancyGuard_init();

        require(
            _clientBasisPoints > 0 && _clientBasisPoints <= 10_000,
            P2pYieldProxy__InvalidClientBasisPoints(_clientBasisPoints)
        );

        s_client = _client;
        s_clientBasisPoints = _clientBasisPoints;

        emit P2pYieldProxy__Initialized();
    }

    /// @inheritdoc IP2pYieldProxy
    function deposit(address _vault, uint256 _amount) external virtual override;

    /// @notice Deposit assets into yield protocol
    /// @param _vault yield-bearing vault token address
    /// @param _callTarget contract that executes the deposit (can differ from _vault)
    /// @param _yieldProtocolDepositCalldata calldata for deposit function of yield protocol
    /// @param _asset asset to deposit
    /// @param _amount amount to deposit
    /// @param _transferBeforeCall whether assets should be transferred to the call target before invoking it
    function _deposit(
        address _vault,
        address _callTarget,
        bytes memory _yieldProtocolDepositCalldata,
        address _asset,
        uint256 _amount,
        bool _transferBeforeCall
    ) internal onlyFactory {
        require(_asset != address(0), P2pYieldProxy__ZeroAddressAsset());
        require(_amount > 0, P2pYieldProxy__ZeroAssetAmount());

        address client = s_client;

        uint256 assetAmountBefore = IERC20(_asset).balanceOf(address(this));

        // transfer tokens into Proxy
        IERC20(_asset).safeTransferFrom(client, address(this), _amount);

        uint256 assetAmountAfter = IERC20(_asset).balanceOf(address(this));
        uint256 actualAmount = assetAmountAfter - assetAmountBefore;

        require(actualAmount == _amount, P2pYieldProxy__DifferentActuallyDepositedAmount(_amount, actualAmount)); // no support for fee-on-transfer or rebasing tokens

        uint256 totalDepositedAfter = s_totalDeposited[_asset] + actualAmount;
        s_totalDeposited[_asset] = totalDepositedAfter;
        emit P2pYieldProxy__Deposited(_vault, _asset, actualAmount, totalDepositedAfter);

        if (_transferBeforeCall) {
            IERC20(_asset).safeTransfer(_callTarget, actualAmount);
        } else {
            IERC20(_asset).safeIncreaseAllowance(_callTarget, actualAmount);
        }

        _callTarget.functionCall(_yieldProtocolDepositCalldata);
    }

    /// @notice Withdraw assets from yield protocol
    /// @param _vault yield-bearing vault token address
    /// @param _asset ERC-20 asset address
    /// @param _callTarget contract that executes the withdrawal (can differ from _vault)
    /// @param _yieldProtocolWithdrawalCalldata calldata for withdraw function of yield protocol
    /// @param _shares amount of vault shares to redeem
    function _withdraw(
        address _vault,
        address _asset,
        address _callTarget,
        bytes memory _yieldProtocolWithdrawalCalldata,
        uint256 _shares
    )
        internal
        nonReentrant
    {
        int256 accruedRewards = calculateAccruedRewards(_vault, _asset);

        uint256 assetAmountBefore = IERC20(_asset).balanceOf(address(this));

        if (_shares > 0) {
            IERC20(_vault).safeIncreaseAllowance(_callTarget, _shares);
        }

        // withdraw assets from Protocol
        _callTarget.functionCall(_yieldProtocolWithdrawalCalldata);

        uint256 assetAmountAfter = IERC20(_asset).balanceOf(address(this));

        uint256 newAssetAmount = assetAmountAfter - assetAmountBefore;

        Withdrawn memory withdrawn = s_totalWithdrawn[_asset];
        uint256 totalWithdrawnBefore = uint256(withdrawn.amount);
        uint256 totalWithdrawnAfter = totalWithdrawnBefore + newAssetAmount;

        // update total withdrawn
        withdrawn.amount = uint208(totalWithdrawnAfter);
        withdrawn.lastFeeCollectionTime = uint48(block.timestamp);
        s_totalWithdrawn[_asset] = withdrawn;

        uint256 p2pAmount;
        if (accruedRewards > 0) {
            // That extra 9999 ensures that any nonzero remainder will push the result up by 1 (ceiling division).
            p2pAmount = (uint256(accruedRewards) * (10_000 - s_clientBasisPoints) + 9999) / 10_000;
        }
        uint256 clientAmount = newAssetAmount - p2pAmount;

        if (p2pAmount > 0) {
            IERC20(_asset).safeTransfer(i_p2pTreasury, p2pAmount);
        }
        // clientAmount must be > 0 at this point
        IERC20(_asset).safeTransfer(s_client, clientAmount);

        emit P2pYieldProxy__Withdrawn(
            _callTarget,
            _vault,
            _asset,
            newAssetAmount,
            totalWithdrawnAfter,
            accruedRewards,
            p2pAmount,
            clientAmount
        );
    }

    /// @inheritdoc IP2pYieldProxy
    function callAnyFunction(address _yieldProtocolAddress, bytes calldata _yieldProtocolCalldata)
        external
        override
        onlyClient
        nonReentrant
        calldataShouldBeAllowed(_yieldProtocolAddress, _yieldProtocolCalldata)
    {
        emit P2pYieldProxy__CalledAsAnyFunction(_yieldProtocolAddress);
        _yieldProtocolAddress.functionCall(_yieldProtocolCalldata);
    }

    /// @notice Returns function selector (first 4 bytes of data)
    /// @param _data calldata (encoded signature + arguments)
    /// @return functionSelector function selector
    function _getFunctionSelector(bytes calldata _data) private pure returns (bytes4 functionSelector) {
        require(_data.length >= 4, P2pYieldProxy__DataTooShort());
        return bytes4(_data[:4]);
    }

    /// @inheritdoc IP2pYieldProxy
    function getFactory() external view override returns (address) {
        return address(i_factory);
    }

    /// @inheritdoc IP2pYieldProxy
    function getP2pTreasury() external view override returns (address) {
        return i_p2pTreasury;
    }

    /// @inheritdoc IP2pYieldProxy
    function getClient() external view override returns (address) {
        return s_client;
    }

    /// @inheritdoc IP2pYieldProxy
    function getClientBasisPoints() external view override returns (uint96) {
        return s_clientBasisPoints;
    }

    /// @inheritdoc IP2pYieldProxy
    function getTotalDeposited(address _asset) external view override returns (uint256) {
        return s_totalDeposited[_asset];
    }

    /// @inheritdoc IP2pYieldProxy
    function getTotalWithdrawn(address _asset) external view override returns (uint256) {
        return s_totalWithdrawn[_asset].amount;
    }

    /// @inheritdoc IP2pYieldProxy
    function getUserPrincipal(address _asset) public view override returns (uint256) {
        uint256 totalDeposited = s_totalDeposited[_asset];
        uint256 totalWithdrawn = s_totalWithdrawn[_asset].amount;
        if (totalDeposited > totalWithdrawn) {
            return totalDeposited - totalWithdrawn;
        }
        return 0;
    }

    /// @inheritdoc IP2pYieldProxy
    function calculateAccruedRewards(address _yieldProtocolAddress, address _asset)
        public
        view
        virtual
        override
        returns (int256)
    {
        uint256 currentAmount = IERC20(_yieldProtocolAddress).balanceOf(address(this));
        uint256 userPrincipal = getUserPrincipal(_asset);
        return int256(currentAmount) - int256(userPrincipal);
    }

    /// @inheritdoc IP2pYieldProxy
    function getLastFeeCollectionTime(address _asset) public view override returns (uint48) {
        return s_totalWithdrawn[_asset].lastFeeCollectionTime;
    }

    /// @inheritdoc ERC165
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IP2pYieldProxy).interfaceId || super.supportsInterface(interfaceId);
    }
}
"
    },
    "src/p2pYieldProxyFactory/IP2pYieldProxyFactory.sol": {
      "content": "// SPDX-FileCopyrightText: 2025 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import "../@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "../common/IAllowedCalldataChecker.sol";

/// @dev External interface of P2pYieldProxyFactory
interface IP2pYieldProxyFactory is IAllowedCalldataChecker, IERC165 {
    /// @dev Emitted when the P2pSigner is transferred
    event P2pYieldProxyFactory__P2pSignerTransferred(address indexed _previousP2pSigner, address indexed _newP2pSigner);

    /// @dev Emitted when the deposit is made
    event P2pYieldProxyFactory__Deposited(address indexed _client, uint96 indexed _clientBasisPoints);

    /// @dev Emitted when the a new proxy is created
    event P2pYieldProxyFactory__ProxyCreated(address _proxy, address _client, uint96 _clientBasisPoints);

    /// @notice Deposits assets for the caller into a specific ERC4626 vault via their proxy
    /// @param _vault The ERC4626 vault that should receive the deposit
    /// @param _amount The amount of assets to deposit
    /// @param _clientBasisPoints The fee share (basis points) that defines the client split
    /// @param _p2pSignerSigDeadline The expiration timestamp for the P2P signer signature
    /// @param _p2pSignerSignature The signature from the P2P signer authorizing the deposit
    /// @return p2pYieldProxyAddress The address of the client-specific yield proxy
    function deposit(
        address _vault,
        uint256 _amount,
        uint96 _clientBasisPoints,
        uint256 _p2pSignerSigDeadline,
        bytes calldata _p2pSignerSignature
    ) external returns (address p2pYieldProxyAddress);

    /// @notice Predicts the address of the deterministic clone for a client and fee share
    /// @param _client The client address that owns the proxy
    /// @param _clientBasisPoints The fee share (basis points) assigned to the client
    /// @return The predicted proxy address
    function predictP2pYieldProxyAddress(address _client, uint96 _clientBasisPoints) external view returns (address);

    /// @notice Transfers control of the P2P signer to a new address
    /// @param _newP2pSigner The address of the new P2P signer
    function transferP2pSigner(address _newP2pSigner) external;

    /// @notice Returns the implementation used for new proxy clones
    /// @return The reference P2pYieldProxy implementation address
    function getReferenceP2pYieldProxy() external view returns (address);

    /// @notice Computes the P2P signer hash required to authorize a deposit
    /// @param _client The client address that will initiate the deposit
    /// @param _clientBasisPoints The client fee share in basis points
    /// @param _p2pSignerSigDeadline The deadline that limits signature validity
    /// @return The digest that must be signed by the P2P signer
    function getHashForP2pSigner(address _client, uint96 _clientBasisPoints, uint256 _p2pSignerSigDeadline)
        external
        view
        returns (bytes32);

    /// @notice Returns the current P2P signer address
    /// @return The address that may authorize deposits
    function getP2pSigner() external view returns (address);

    /// @notice Returns the address of the active P2P operator
    /// @return The operator address
    function getP2pOperator() external view returns (address);

    /// @notice Returns all proxies that have been created by the factory
    /// @return The list of proxy addresses
    function getAllProxies() external view returns (address[] memory);
}
"
    },
    "src/@openzeppelin/contracts/interfaces/IERC4626.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol)

pragma solidity 0.8.30;

import "../token/ERC20/IERC20.sol";
import "../token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 *
 * _Available since v4.7._
 */
interface IERC4626 is IERC20, IERC20Metadata {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
    );

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address assetTokenAddress);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
     * through a deposit call.
     *
     * - MUST return a limited value if receiver is subject to some deposit limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
     * - MUST NOT revert.
     */
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
     *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
     *   in the same transaction.
     * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
     *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   deposit execution, and are accounted for during deposit.
     * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
     * - MUST return a limited value if receiver is subject to some mint limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
     * - MUST NOT revert.
     */
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
     *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
     *   same transaction.
     * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
     *   would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by minting.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
     *   execution, and are accounted for during mint.
     * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
     * Vault, through a withdraw call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
     *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
     *   called
     *   in the same transaction.
     * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
     *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewWithdraw(uint256 assets) externa

Tags:
ERC20, ERC165, Proxy, Mintable, Yield, Upgradeable, Factory|addr:0x2ee972199319d11a5765ba5aeef6ce7db369708a|verified:true|block:23668513|tx:0x898a1e216f61d815b5c6df23ac3618440fb6fbd8f4add15cc9f175ecb9770f22|first_check:1761568069

Submitted on: 2025-10-27 13:27:49

Comments

Log in to comment.

No comments yet.