P2PLending

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts_prod/P2PLending.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
import "@openzeppelin/contracts/access/Ownable.sol";\r
import "@openzeppelin/contracts/utils/math/Math.sol";\r
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";\r
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";\r
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";\r
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";\r
import "./interface/IERC20_Decimals.sol";\r
import "./interface/IP2PLending.sol";\r
\r
contract P2PLending is IP2PLending, Ownable, ERC1155Supply, ReentrancyGuard\r
{\r
    using SafeERC20 for IERC20;\r
    \r
    bool public stopped = false;\r
    uint96 constant public CLAIM_PERIOD = 2 days;\r
    uint8 constant public RATIOS_DECIMALS = 4;\r
    uint96 constant public WITHDRAW_COOLDOWN = 30 seconds;\r
    uint public LOT_SIZE = 10; // 10 USDC in wei after constructor\r
    uint32 public MIN_LOTS_AMOUNT = 10;\r
    uint32 public MIN_DURATION = 7; // days\r
    uint32 public MAX_DURATION = 365; // days\r
    uint32 public MIN_FILL_DURATION = 1; // days   \r
    uint32 public MIN_APY = 100; // 100 = 1%\r
    uint32 public MAX_APY = 100000; // 1000% \r
    uint32 public TARGET_RELATIVE_VALUE = 13000; // 130%\r
    uint32 public LIQUIDATION_RELATIVE_VALUE = 11500; // 115%\r
    uint32 public PROTOCOL_FEE = 150; // 1.5%\r
    address public FEES_COLLECTOR; \r
    IPriceData public PRICE_ORACLE;\r
\r
    address immutable public COLLATERAL;\r
    uint8 immutable private COLLATERAL_DECIMALS;\r
    IERC20 immutable public USDC; \r
\r
    mapping (uint => LoanConditions) private loan_conditions;\r
    mapping (uint => LoanState) private loan_state;\r
    uint private next_loan_id = 1;\r
    mapping (address => mapping(uint => uint96)) private _withdraw_cooldown;\r
\r
    constructor(address _price_oracle_address, address _COLLATERAL, address _USDC, address _FEES_COLLECTOR, string memory _erc1155_uri) Ownable(_msgSender()) ERC1155(_erc1155_uri)\r
    {\r
        PRICE_ORACLE = IPriceData(_price_oracle_address);\r
        COLLATERAL = _COLLATERAL;\r
        COLLATERAL_DECIMALS = IERC20_Decimals(COLLATERAL).decimals();\r
        USDC = IERC20(_USDC);\r
        LOT_SIZE = LOT_SIZE*10**IERC20_Decimals(_USDC).decimals();\r
        FEES_COLLECTOR = _FEES_COLLECTOR;\r
    }\r
\r
    modifier validate_loan(uint loan_id) \r
    {\r
        require(loan_id > 0 && loan_id < next_loan_id, "Invalid Loan ID!");\r
        _;\r
    }\r
\r
    function time_now() private view returns(uint96)\r
    {\r
        return uint96(block.timestamp);\r
    }\r
\r
    function emit_state_updated(uint loan_id) private \r
    {\r
        emit StateUpdated(loan_id, loan_state[loan_id].borrower, loan_state[loan_id].deadline, loan_state[loan_id].collateral_balance, loan_state[loan_id].USDC_balance, loan_state[loan_id].claim_deadline);\r
    }\r
\r
    function usdc_to_collateral(uint usdc_amount, IPriceData price_oracle, bytes calldata offchain_price_data) internal returns(uint)\r
    {\r
        return price_oracle.useQuoteAmountToCollateralAmount(COLLATERAL, COLLATERAL_DECIMALS, usdc_amount, offchain_price_data);\r
    }\r
\r
    function getDebt(uint loan_id) public view returns(uint)\r
    {\r
        LoanState storage state = loan_state[loan_id];\r
        if(state.deadline == 0 || state.collateral_balance == 0)\r
        {\r
            return 0;\r
        }\r
        LoanConditions storage conditions = loan_conditions[loan_id];\r
        uint USDC_required = totalSupply(loan_id)*conditions.lot_size;\r
        uint since_start = state.deadline > time_now() ? time_now() - (state.deadline - conditions.duration) : conditions.duration - 1;\r
        since_start = (since_start/(1 days) + 1)*(1 days);\r
        return USDC_required + since_start*conditions.apy*USDC_required/(10**RATIOS_DECIMALS)/(365 days);\r
    }\r
\r
    function getLoanStatus(uint loan_id) public view returns(LoanStatus)\r
    {\r
        LoanConditions storage conditions = loan_conditions[loan_id];\r
        LoanState storage state = loan_state[loan_id];\r
        if(conditions.lots_required == 0)\r
        {\r
            return LoanStatus.UNDEFINED;\r
        }\r
        else if((state.borrower == address(0) && totalSupply(loan_id) == 0) \r
            || (state.borrower != address(0) && state.collateral_balance == 0))\r
        {\r
            return LoanStatus.FINISHED;\r
        }\r
        else if(state.deadline == 0)\r
        {\r
            if(state.claim_deadline != 0 && state.claim_deadline < time_now())\r
            {\r
                return LoanStatus.EXPIRED;\r
            }\r
            else if(conditions.lots_required > totalSupply(loan_id))\r
            {\r
                return LoanStatus.FINANCING;\r
            }\r
            else\r
            {\r
                return LoanStatus.FULLY_FUNDED;\r
            }\r
        }\r
        else if(state.collateral_balance > 0)\r
        {\r
            if(totalSupply(loan_id) > 0)\r
            {\r
                return LoanStatus.ACTIVE;\r
            }\r
            else \r
            {\r
                return LoanStatus.FINISHED;\r
            }\r
        }\r
        else\r
        {\r
            return LoanStatus.FINISHED;\r
        }\r
    }\r
\r
    function createAsBorrower(uint lots, uint max_collateral_out, uint32 collateral_value, uint32 duration_days, uint32 apy, uint32 fill_duration_days, bytes calldata offchain_price_data) external nonReentrant returns(uint)\r
    {\r
        require(!stopped, "P2PLending:STOPPED!");\r
        require(lots >= MIN_LOTS_AMOUNT \r
            && collateral_value >= TARGET_RELATIVE_VALUE\r
            && duration_days >= MIN_DURATION && duration_days <= MAX_DURATION\r
            && apy >= MIN_APY && apy <= MAX_APY\r
            && fill_duration_days >= MIN_FILL_DURATION\r
            , "P2PLending:Invalid arguments!");\r
        uint USDC_amount = LOT_SIZE*lots;\r
        uint collateral_amount = collateral_value*usdc_to_collateral(USDC_amount, PRICE_ORACLE, offchain_price_data)/10**RATIOS_DECIMALS;\r
        require(max_collateral_out >= collateral_amount && collateral_amount > 0, "P2PLending:Increase max_collateral_out!");\r
\r
        IERC20(COLLATERAL).safeTransferFrom(_msgSender(), address(this), collateral_amount);\r
        loan_conditions[next_loan_id] = LoanConditions({ \r
            lots_required: lots,\r
            lot_size: LOT_SIZE,\r
            price_oracle: PRICE_ORACLE,\r
            duration: duration_days * (1 days),\r
            apy: apy,\r
            target_relative_value: collateral_value,\r
            liquidation_relative_value: LIQUIDATION_RELATIVE_VALUE,\r
            fill_deadline: time_now() + fill_duration_days * (1 days)\r
        });\r
\r
        loan_state[next_loan_id] = LoanState({ \r
            borrower: _msgSender(),\r
            deadline: 0,\r
            collateral_balance: collateral_amount,\r
            claim_deadline: 0,\r
            USDC_balance: 0    \r
        });\r
        \r
        emit CreateAsBorrower(next_loan_id, loan_conditions[next_loan_id].lots_required, loan_conditions[next_loan_id].lot_size, address(loan_conditions[next_loan_id].price_oracle), loan_conditions[next_loan_id].duration, loan_conditions[next_loan_id].apy, loan_conditions[next_loan_id].target_relative_value, loan_conditions[next_loan_id].liquidation_relative_value,loan_conditions[next_loan_id].fill_deadline);\r
        emit_state_updated(next_loan_id);\r
        return next_loan_id++;\r
    }\r
\r
    function createAsLender(uint lots, uint32 collateral_value, uint32 duration_days, uint32 apy) external nonReentrant returns(uint)\r
    {\r
        require(!stopped, "P2PLending:STOPPED!");\r
        require(lots >= MIN_LOTS_AMOUNT \r
            && collateral_value >= TARGET_RELATIVE_VALUE\r
            && duration_days >= MIN_DURATION && duration_days <= MAX_DURATION\r
            && apy >= MIN_APY && apy <= MAX_APY\r
            , "P2PLending:Invalid arguments!");\r
        USDC.safeTransferFrom(_msgSender(), address(this), LOT_SIZE*lots);\r
        _mint(_msgSender(), next_loan_id, lots, "");\r
        \r
        loan_conditions[next_loan_id] = LoanConditions({ \r
            lots_required: lots,\r
            lot_size: LOT_SIZE,\r
            price_oracle: PRICE_ORACLE,\r
            duration: duration_days * (1 days),\r
            apy: apy,\r
            target_relative_value: collateral_value,\r
            liquidation_relative_value: LIQUIDATION_RELATIVE_VALUE,\r
            fill_deadline: 0\r
        });\r
\r
        loan_state[next_loan_id] = LoanState({ \r
            borrower: address(0),\r
            deadline: 0,\r
            collateral_balance: 0,\r
            claim_deadline: 0,\r
            USDC_balance: 0     \r
        });\r
\r
        emit CreateAsLender(next_loan_id, _msgSender(), loan_conditions[next_loan_id].lots_required, loan_conditions[next_loan_id].lot_size, address(loan_conditions[next_loan_id].price_oracle), loan_conditions[next_loan_id].duration, loan_conditions[next_loan_id].apy, loan_conditions[next_loan_id].target_relative_value, loan_conditions[next_loan_id].liquidation_relative_value,loan_conditions[next_loan_id].fill_deadline);\r
        return next_loan_id++;\r
    }\r
\r
    function lend(uint loan_id, uint target_lots, uint min_lots) external nonReentrant validate_loan(loan_id) returns(uint)\r
    {\r
        require(target_lots > 0 && target_lots >= min_lots, "P2PLending require:target_lots>0 && lots>=min_lots");\r
        require(getLoanStatus(loan_id) == LoanStatus.FINANCING, "P2PLending:LoanStatus not equals FINANCING!");\r
        \r
        LoanConditions storage conditions = loan_conditions[loan_id];\r
        uint available_lots = conditions.lots_required - totalSupply(loan_id);\r
        require(available_lots >= min_lots, "P2PLending:Not enough available lots!");\r
        uint lots = Math.min(target_lots, available_lots);\r
        USDC.safeTransferFrom(_msgSender(), address(this), conditions.lot_size*lots);\r
        _mint(_msgSender(), loan_id, lots, "");\r
        require(totalSupply(loan_id) <= conditions.lots_required, "P2PLending require:supply<=lots_required");\r
        uint96 epoch_now = time_now();\r
        if(totalSupply(loan_id) == conditions.lots_required)\r
        {\r
            loan_state[loan_id].claim_deadline = epoch_now + CLAIM_PERIOD;\r
            emit FullyFunded(loan_id, loan_state[loan_id].claim_deadline);\r
        }\r
        else \r
        {\r
            _withdraw_cooldown[_msgSender()][loan_id] = epoch_now + WITHDRAW_COOLDOWN;\r
        }\r
\r
        emit Lend(loan_id, _msgSender(), lots);\r
        return lots;\r
    }\r
\r
    function borrow(uint loan_id, uint min_lots, uint max_lots, uint32 target_relative_value, uint min_collateral_in, uint max_collateral_out,  bytes calldata offchain_price_data) external nonReentrant validate_loan(loan_id) returns(uint)\r
    {\r
        LoanConditions storage conditions = loan_conditions[loan_id];\r
        LoanState storage state = loan_state[loan_id];\r
        {\r
        uint available_lots = totalSupply(loan_id);\r
        require(min_lots>0 && available_lots >= min_lots && available_lots <= max_lots,"P2PLending require:0<min_lots<=supply<=max_lots");\r
        LoanStatus status = getLoanStatus(loan_id);\r
        require(status == LoanStatus.FINANCING || status == LoanStatus.FULLY_FUNDED, "P2PLending:Cannot borrow!");     \r
        require(state.borrower == _msgSender() || state.borrower == address(0), "P2PLending:Caller is not owner!");\r
        \r
        conditions.lots_required = available_lots;\r
        state.borrower = _msgSender();\r
        state.deadline = time_now() + conditions.duration;\r
        }\r
        uint USDC_required = conditions.lots_required*conditions.lot_size;\r
        uint collateral_amount_100 = usdc_to_collateral(USDC_required, conditions.price_oracle, offchain_price_data);\r
        uint target_collateral_amount = conditions.target_relative_value*collateral_amount_100/10**RATIOS_DECIMALS;\r
        if(target_relative_value != 0 || min_collateral_in > 0)\r
        {\r
            require(target_relative_value >= conditions.target_relative_value, "P2PLending:Increase target_relative_value!");\r
            target_collateral_amount = Math.max(target_collateral_amount, target_relative_value*collateral_amount_100/10**RATIOS_DECIMALS);\r
            if(target_collateral_amount > state.collateral_balance)\r
            {\r
                uint collateral_out = target_collateral_amount - state.collateral_balance;\r
                require(max_collateral_out >= collateral_out, "P2PLending:Increase max_collateral_out!");\r
                IERC20(COLLATERAL).safeTransferFrom(state.borrower, address(this), collateral_out);\r
            }\r
            else if(target_collateral_amount < state.collateral_balance)\r
            {\r
                uint collateral_in = state.collateral_balance - target_collateral_amount;\r
                require(collateral_in >= min_collateral_in, "P2PLending:Decrease min_collateral_in!");\r
                IERC20(COLLATERAL).safeTransfer(state.borrower, collateral_in);\r
            }\r
            state.collateral_balance = target_collateral_amount;\r
        }\r
        require(state.collateral_balance >= target_collateral_amount, "P2PLending:Increase collateral!");\r
        {\r
        uint fee = PROTOCOL_FEE*USDC_required/(10**RATIOS_DECIMALS);\r
        USDC.safeTransfer(FEES_COLLECTOR, fee);\r
        USDC.safeTransfer(state.borrower, USDC_required - fee);\r
        }\r
        emit Borrow(loan_id, conditions.lots_required);\r
        emit_state_updated(loan_id); \r
        return conditions.lots_required;\r
    }\r
\r
    function repay(uint loan_id) external nonReentrant validate_loan(loan_id)\r
    {\r
        require(getLoanStatus(loan_id) == LoanStatus.ACTIVE, "P2PLending:LoanStatus must equals ACTIVE!");\r
        LoanState storage state = loan_state[loan_id];\r
        require(state.borrower == _msgSender(), "P2PLending:Caller is not owner!");\r
        uint required_USDC = getDebt(loan_id);\r
        USDC.safeTransferFrom(state.borrower, address(this), required_USDC);\r
        state.USDC_balance += required_USDC;\r
        IERC20(COLLATERAL).safeTransfer(state.borrower, state.collateral_balance);\r
        state.collateral_balance = 0;\r
\r
        emit Repay(loan_id, required_USDC);\r
        emit_state_updated(loan_id);\r
    }\r
\r
    function increaseCollateral(uint loan_id, uint collateral_amount) external nonReentrant validate_loan(loan_id)\r
    {\r
        require(getLoanStatus(loan_id) == LoanStatus.ACTIVE, "P2PLending:LoanStatus must equals ACTIVE!");\r
        LoanState storage state = loan_state[loan_id];\r
        require(state.borrower == _msgSender(), "P2PLending:Caller is not owner!");\r
        IERC20(COLLATERAL).safeTransferFrom(state.borrower, address(this), collateral_amount);\r
        state.collateral_balance += collateral_amount;\r
\r
        emit IncreaseCollateral(loan_id, collateral_amount);\r
        emit_state_updated(loan_id);\r
    }\r
\r
    function withdrawCollateral(uint loan_id) external nonReentrant validate_loan(loan_id)\r
    {\r
        LoanState storage state = loan_state[loan_id];\r
        require(state.borrower == _msgSender(), "P2PLending:Caller is not owner!");\r
        require(state.deadline == 0 && state.collateral_balance > 0, "P2PLending:Cannot Withdraw!");\r
        if(getLoanStatus(loan_id) == LoanStatus.FINANCING && loan_conditions[loan_id].fill_deadline < time_now())\r
        {\r
            IERC20(COLLATERAL).safeTransfer(state.borrower, state.collateral_balance);\r
        }\r
        else \r
        {\r
            uint fee = PROTOCOL_FEE*state.collateral_balance/(10**RATIOS_DECIMALS);\r
            IERC20(COLLATERAL).safeTransfer(FEES_COLLECTOR, fee);\r
            IERC20(COLLATERAL).safeTransfer(state.borrower, state.collateral_balance - fee);\r
        }    \r
        state.collateral_balance = 0;\r
        state.USDC_balance = totalSupply(loan_id)*loan_conditions[loan_id].lot_size;\r
\r
        emit WithdrawCollateral(loan_id);\r
        emit_state_updated(loan_id);\r
    }\r
\r
    function withdrawUSDC(uint loan_id, uint lots) external nonReentrant validate_loan(loan_id)\r
    {\r
        require(lots > 0, "P2PLending:lots must be greater than zero!");\r
        LoanStatus status = getLoanStatus(loan_id);\r
        require(loan_state[loan_id].borrower == address(0) || status == LoanStatus.EXPIRED || (status == LoanStatus.FINANCING && time_now() > _withdraw_cooldown[_msgSender()][loan_id]), "P2PLending:Cannot withdraw!");\r
        _burn(_msgSender(), loan_id, lots);\r
        if(loan_state[loan_id].borrower == address(0))\r
        {\r
            uint supply_after_burn = totalSupply(loan_id);\r
            require(supply_after_burn == 0 || supply_after_burn >= MIN_LOTS_AMOUNT,"P2PLending:Supply below MIN_LOTS_AMOUNT!");\r
        }\r
        USDC.safeTransfer(_msgSender(), loan_conditions[loan_id].lot_size*lots);\r
\r
        emit WithdrawUSDC(loan_id, _msgSender(), lots);\r
    }\r
\r
    function claimUSDC(uint loan_id) external nonReentrant validate_loan(loan_id)\r
    {\r
        require(getLoanStatus(loan_id) == LoanStatus.FINISHED, "P2PLending:LoanStatus must equals FINISHED!");\r
        uint loan_tokens_amount = balanceOf(_msgSender(), loan_id);\r
        require(loan_tokens_amount > 0, "P2PLending:Nothing to claim!");\r
        LoanState storage state = loan_state[loan_id];    \r
        uint USDC_amount = loan_tokens_amount*state.USDC_balance/totalSupply(loan_id);   \r
        state.USDC_balance -= USDC_amount;\r
        _burn(_msgSender(), loan_id, loan_tokens_amount);\r
        USDC.safeTransfer(_msgSender(), USDC_amount);\r
\r
        emit ClaimUSDC(loan_id, _msgSender(), loan_tokens_amount, USDC_amount);\r
        emit_state_updated(loan_id);\r
    }\r
\r
    function claimCollateral(uint loan_id, bytes calldata offchain_price_data) external nonReentrant validate_loan(loan_id)\r
    {\r
        require(getLoanStatus(loan_id) == LoanStatus.ACTIVE, "P2PLending:LoanStatus must equals ACTIVE!");\r
        LoanConditions storage conditions = loan_conditions[loan_id];\r
        LoanState storage state = loan_state[loan_id];\r
        require(state.deadline < time_now() || conditions.price_oracle.useRelativeCollateralValue(COLLATERAL, RATIOS_DECIMALS, state.collateral_balance, COLLATERAL_DECIMALS, getDebt(loan_id), offchain_price_data) < conditions.liquidation_relative_value, "P2PLending:Cannot Liquidate!");      \r
        uint loan_tokens_amount = balanceOf(_msgSender(), loan_id);\r
        require(loan_tokens_amount > 0, "P2PLending:Nothing to claim!");\r
        \r
        uint collateral_amount = loan_tokens_amount*state.collateral_balance/totalSupply(loan_id); \r
        state.collateral_balance -= collateral_amount;\r
        _burn(_msgSender(), loan_id, loan_tokens_amount);\r
        IERC20(COLLATERAL).safeTransfer(_msgSender(), collateral_amount);\r
\r
        emit ClaimCollateral(loan_id, _msgSender(), loan_tokens_amount, collateral_amount);\r
        emit_state_updated(loan_id);\r
    }\r
\r
    function isCollateralClaimable(uint loan_id, bytes calldata offchain_price_data) external view returns(bool)\r
    {\r
        if(!(loan_id > 0 && loan_id < next_loan_id)\r
            || getLoanStatus(loan_id) != LoanStatus.ACTIVE\r
            || !(loan_state[loan_id].deadline < time_now() || loan_conditions[loan_id].price_oracle.readRelativeCollateralValue(COLLATERAL, RATIOS_DECIMALS, loan_state[loan_id].collateral_balance, COLLATERAL_DECIMALS, getDebt(loan_id), offchain_price_data) < loan_conditions[loan_id].liquidation_relative_value))\r
        {\r
            return false;\r
        }\r
        else\r
        {\r
            return true;\r
        }\r
    }\r
\r
    function getRelativeCollateralValue(uint loan_id, bytes calldata offchain_price_data) external validate_loan(loan_id) view returns(uint)\r
    {\r
        require(getLoanStatus(loan_id) == LoanStatus.ACTIVE, "P2PLending:LoanStatus must equals ACTIVE!");\r
        return loan_conditions[loan_id].price_oracle.readRelativeCollateralValue(COLLATERAL, RATIOS_DECIMALS, loan_state[loan_id].collateral_balance, COLLATERAL_DECIMALS, getDebt(loan_id), offchain_price_data);\r
    }\r
\r
    function USDCValueOf(uint loan_id, uint amount) external validate_loan(loan_id) view returns(uint)\r
    {\r
        uint supply = totalSupply(loan_id);\r
        if(supply > 0)\r
        {\r
            uint loan_USDC_value = loan_state[loan_id].USDC_balance == 0 ? getDebt(loan_id) : loan_state[loan_id].USDC_balance;\r
            return amount*loan_USDC_value/supply;\r
        }\r
        else \r
        {\r
            return 0;\r
        }\r
    }\r
\r
    function getLoanInfo(uint loan_id) external validate_loan(loan_id) view returns(LoanConditions memory, LoanState memory, uint)\r
    {\r
        return (loan_conditions[loan_id], loan_state[loan_id], totalSupply(loan_id));\r
    }\r
\r
    function setLotSize(uint _LOT_SIZE) external onlyOwner\r
    {\r
        require(_LOT_SIZE > 0);\r
        LOT_SIZE = _LOT_SIZE;\r
        emit SetLotSize(LOT_SIZE);\r
    }\r
\r
    function setMinLotsAmount(uint32 _MIN_LOTS_AMOUNT) external onlyOwner\r
    {\r
        require(_MIN_LOTS_AMOUNT > 0);\r
        MIN_LOTS_AMOUNT = _MIN_LOTS_AMOUNT;\r
        emit SetMinLotsAmount(MIN_LOTS_AMOUNT);\r
    }\r
\r
    function setMinDuration(uint32 _MIN_DURATION) external onlyOwner\r
    {\r
        require(_MIN_DURATION > 0);\r
        require(MAX_DURATION >= _MIN_DURATION);\r
        MIN_DURATION = _MIN_DURATION;\r
        emit SetMinDuration(MIN_DURATION);\r
    }\r
\r
    function setMaxDuration(uint32 _MAX_DURATION) external onlyOwner\r
    {\r
        require(_MAX_DURATION >= MIN_DURATION);\r
        MAX_DURATION = _MAX_DURATION;\r
        emit SetMaxDuration(MAX_DURATION);\r
    }\r
\r
    function setMinFillDuration(uint32 _MIN_FILL_DURATION) external onlyOwner\r
    {\r
        MIN_FILL_DURATION = _MIN_FILL_DURATION;\r
        emit SetMinFillDuration(MIN_FILL_DURATION);\r
    }\r
\r
    function setMinAPY(uint32 _MIN_APY) external onlyOwner\r
    {\r
        require(MAX_APY >= _MIN_APY);\r
        MIN_APY = _MIN_APY;\r
        emit SetMinAPY(MIN_APY);\r
    }\r
\r
    function setMaxAPY(uint32 _MAX_APY) external onlyOwner\r
    {\r
        require(_MAX_APY >= MIN_APY);\r
        MAX_APY = _MAX_APY;\r
        emit SetMaxAPY(MAX_APY);\r
    }\r
\r
    function setTargetRelativeValue(uint32 _TARGET_RELATIVE_VALUE) external onlyOwner\r
    {\r
        require(_TARGET_RELATIVE_VALUE >= LIQUIDATION_RELATIVE_VALUE);\r
        TARGET_RELATIVE_VALUE = _TARGET_RELATIVE_VALUE;\r
        emit SetTargetRelativeValue(TARGET_RELATIVE_VALUE);\r
    }\r
\r
    function setLiquidationRelativeValue(uint32 _LIQUIDATION_RELATIVE_VALUE) external onlyOwner\r
    {\r
        require(_LIQUIDATION_RELATIVE_VALUE > 10**RATIOS_DECIMALS);\r
        require(TARGET_RELATIVE_VALUE >= _LIQUIDATION_RELATIVE_VALUE);\r
        LIQUIDATION_RELATIVE_VALUE = _LIQUIDATION_RELATIVE_VALUE;\r
        emit SetLiquidationRelativeValue(LIQUIDATION_RELATIVE_VALUE);\r
    }\r
\r
    function setProtocolFee(uint32 _PROTOCOL_FEE) external onlyOwner\r
    {\r
        PROTOCOL_FEE = _PROTOCOL_FEE;\r
        emit SetProtocolFee(PROTOCOL_FEE);\r
    }\r
\r
    function setFeesCollector(address _FEES_COLLECTOR) external onlyOwner\r
    {\r
        FEES_COLLECTOR = _FEES_COLLECTOR;\r
        emit SetFeesCollector(FEES_COLLECTOR);\r
    }\r
\r
    function setPriceOracle(address _PRICE_ORACLE) external onlyOwner\r
    {\r
        PRICE_ORACLE = IPriceData(_PRICE_ORACLE);\r
        emit SetPriceOracle(_PRICE_ORACLE);\r
    }\r
\r
    event Stopped();\r
    function stop() external onlyOwner\r
    {\r
        require(!stopped);\r
        stopped = true;\r
        emit Stopped();\r
    }\r
\r
}\r
"
    },
    "contracts_prod/interface/IP2PLending.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
import "./IPriceData.sol";\r
\r
interface IP2PLending\r
{\r
   struct LoanConditions\r
    { \r
        uint lots_required;\r
        uint lot_size;\r
        IPriceData price_oracle;\r
        uint96 duration;\r
        uint32 apy;\r
        uint32 target_relative_value;\r
        uint32 liquidation_relative_value;\r
        uint96 fill_deadline;\r
    }\r
\r
    struct LoanState\r
    {\r
        address borrower;\r
        uint96 deadline;\r
        uint collateral_balance;\r
        uint USDC_balance;\r
        uint96 claim_deadline;\r
    }\r
\r
    enum LoanStatus\r
    {\r
        UNDEFINED,\r
        FINANCING,\r
        FULLY_FUNDED,\r
        EXPIRED,\r
        ACTIVE,\r
        FINISHED\r
    }\r
    \r
    event CreateAsBorrower(uint indexed loan_id, uint lots_required, uint lot_size, address price_oracle, uint96 duration, uint32 apy, uint32 target_relative_value, uint32 liquidation_relative_value, uint96 fill_deadline);\r
    event CreateAsLender(uint indexed loan_id, address indexed lender, uint lots_required, uint lot_size, address price_oracle, uint96 duration, uint32 apy, uint32 target_relative_value, uint32 liquidation_relative_value, uint96 fill_deadline);\r
    event StateUpdated(uint indexed loan_id, address indexed borrower, uint96 deadline, uint collateral_balance, uint USDC_balance, uint96 claim_deadline);\r
    event FullyFunded(uint indexed loan_id, uint96 claim_deadline);\r
    event Lend(uint indexed loan_id, address indexed lender, uint lots);\r
    event Borrow(uint indexed loan_id, uint lots);\r
    event Repay(uint indexed loan_id, uint USDC_amount);\r
    event IncreaseCollateral(uint indexed load_id, uint collateral_amount);\r
    event WithdrawCollateral(uint indexed load_id);\r
    event WithdrawUSDC(uint indexed loan_id, address indexed lender, uint lots);\r
    event ClaimUSDC(uint indexed loan_id, address indexed lender, uint lots, uint USDC_amount); \r
    event ClaimCollateral(uint indexed loan_id, address indexed lender, uint lots, uint collateral_amount);\r
\r
    function getDebt(uint loan_id) external view returns(uint USDC_value);\r
\r
    function getLoanStatus(uint loan_id) external view returns(LoanStatus status);\r
\r
    function createAsBorrower(uint lots, uint max_collateral_out, uint32 collateral_value, uint32 duration_days, uint32 apy, uint32 fill_duration_days, bytes calldata offchain_price_data) external returns(uint loan_id);\r
\r
    function createAsLender(uint lots, uint32 collateral_value, uint32 duration_days, uint32 apy) external returns(uint loan_id);\r
\r
    function lend(uint loan_id, uint target_lots, uint min_lots) external returns(uint lots);\r
\r
    function borrow(uint loan_id, uint min_lots, uint max_lots, uint32 target_relative_value, uint min_collateral_in, uint max_collateral_out, bytes calldata offchain_price_data) external returns(uint lots);\r
    \r
    function repay(uint loan_id) external;\r
\r
    function withdrawCollateral(uint loan_id) external;\r
\r
    function withdrawUSDC(uint loan_id, uint lots) external;\r
\r
    function claimUSDC(uint loan_id) external;\r
\r
    function claimCollateral(uint loan_id, bytes calldata offchain_price_data) external;\r
\r
    function isCollateralClaimable(uint loan_id, bytes calldata offchain_price_data) external view returns(bool);\r
\r
    function getRelativeCollateralValue(uint loan_id, bytes calldata offchain_price_data) external view returns(uint relatie_value);\r
\r
    function USDCValueOf(uint loan_id, uint amount) external view returns(uint USDC_value);\r
\r
    function getLoanInfo(uint loan_id) external view returns(LoanConditions memory conditions, LoanState memory state, uint supply);\r
\r
    event SetLotSize(uint LOT_SIZE);\r
    event SetMinLotsAmount(uint32 MIN_LOTS_AMOUNT);\r
    event SetMinDuration(uint32 MIN_LOTS_AMOUNT);\r
    event SetMaxDuration(uint32 MIN_LOTS_AMOUNT);\r
    event SetMinFillDuration(uint32 MIN_FILL_DURATION);\r
    event SetMinAPY(uint32 MIN_APY);\r
    event SetMaxAPY(uint32 MAX_APY);\r
    event SetTargetRelativeValue(uint32 TARGET_RELATIVE_VALUE);\r
    event SetLiquidationRelativeValue(uint32 LIQUIDATION_RELATIVE_VALUE);\r
    event SetProtocolFee(uint32 PROTOCOL_FEE);\r
    event SetFeesCollector(address FEES_COLLECTOR);\r
    event SetPriceOracle(address PRICE_ORACLE);\r
\r
}\r
"
    },
    "contracts_prod/interface/IERC20_Decimals.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
interface IERC20_Decimals \r
{\r
    function decimals() external view returns (uint8);   \r
}\r
"
    },
    "@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @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 EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * 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 ReentrancyGuard {
    // 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;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _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 {
        // 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 {
        // 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) {
        return _status == ENTERED;
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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);
}
"
    },
    "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/ERC1155Supply.sol)

pragma solidity ^0.8.20;

import {ERC1155} from "../ERC1155.sol";
import {Arrays} from "../../../utils/Arrays.sol";

/**
 * @dev Extension of ERC-1155 that adds tracking of total supply per id.
 *
 * Useful for scenarios where Fungible and Non-fungible tokens have to be
 * clearly identified. Note: While a totalSupply of 1 might mean the
 * corresponding is an NFT, there is no guarantees that no other token with the
 * same id are not going to be minted.
 *
 * NOTE: This contract implies a global limit of 2**256 - 1 to the number of tokens
 * that can be minted.
 *
 * CAUTION: This extension should not be added in an upgrade to an already deployed contract.
 */
abstract contract ERC1155Supply is ERC1155 {
    using Arrays for uint256[];

    mapping(uint256 id => uint256) private _totalSupply;
    uint256 private _totalSupplyAll;

    /**
     * @dev Total value of tokens in with a given id.
     */
    function totalSupply(uint256 id) public view virtual returns (uint256) {
        return _totalSupply[id];
    }

    /**
     * @dev Total value of tokens.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupplyAll;
    }

    /**
     * @dev Indicates whether any token exist with a given id, or not.
     */
    function exists(uint256 id) public view virtual returns (bool) {
        return totalSupply(id) > 0;
    }

    /**
     * @dev See {ERC1155-_update}.
     */
    function _update(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values
    ) internal virtual override {
        super._update(from, to, ids, values);

        if (from == address(0)) {
            uint256 totalMintValue = 0;
            for (uint256 i = 0; i < ids.length; ++i) {
                uint256 value = values.unsafeMemoryAccess(i);
                // Overflow check required: The rest of the code assumes that totalSupply never overflows
                _totalSupply[ids.unsafeMemoryAccess(i)] += value;
                totalMintValue += value;
            }
            // Overflow check required: The rest of the code assumes that totalSupplyAll never overflows
            _totalSupplyAll += totalMintValue;
        }

        if (to == address(0)) {
            uint256 totalBurnValue = 0;
            for (uint256 i = 0; i < ids.length; ++i) {
                uint256 value = values.unsafeMemoryAccess(i);

                unchecked {
                    // Overflow not possible: values[i] <= balanceOf(from, ids[i]) <= totalSupply(ids[i])
                    _totalSupply[ids.unsafeMemoryAccess(i)] -= value;
                    // Overflow not possible: sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll
                    totalBurnValue += value;
                }
            }
            unchecked {
                // Overflow not possible: totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll
                _totalSupplyAll -= totalBurnValue;
            }
        }
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC1155/ERC1155.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/ERC1155.sol)

pragma solidity ^0.8.20;

import {IERC1155} from "./IERC1155.sol";
import {IERC1155MetadataURI} from "./extensions/IERC1155MetadataURI.sol";
import {ERC1155Utils} from "./utils/ERC1155Utils.sol";
import {Context} from "../../utils/Context.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {Arrays} from "../../utils/Arrays.sol";
import {IERC1155Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the basic standard multi-token.
 * See https://eips.ethereum.org/EIPS/eip-1155
 * Originally based on code by Enjin: https://github.com/enjin/erc-1155
 */
abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors {
    using Arrays for uint256[];
    using Arrays for address[];

    mapping(uint256 id => mapping(address account => uint256)) private _balances;

    mapping(address account => mapping(address operator => bool)) private _operatorApprovals;

    // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
    string private _uri;

    /**
     * @dev See {_setURI}.
     */
    constructor(string memory uri_) {
        _setURI(uri_);
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC1155).interfaceId ||
            interfaceId == type(IERC1155MetadataURI).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC1155MetadataURI-uri}.
     *
     * This implementation returns the same URI for *all* token types. It relies
     * on the token type ID substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the ERC].
     *
     * Clients calling this function must replace the `\{id\}` substring with the
     * actual token type ID.
     */
    function uri(uint256 /* id */) public view virtual returns (string memory) {
        return _uri;
    }

    /**
     * @dev See {IERC1155-balanceOf}.
     */
    function balanceOf(address account, uint256 id) public view virtual returns (uint256) {
        return _balances[id][account];
    }

    /**
     * @dev See {IERC1155-balanceOfBatch}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] memory accounts,
        uint256[] memory ids
    ) public view virtual returns (uint256[] memory) {
        if (accounts.length != ids.length) {
            revert ERC1155InvalidArrayLength(ids.length, accounts.length);
        }

        uint256[] memory batchBalances = new uint256[](accounts.length);

        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts.unsafeMemoryAccess(i), ids.unsafeMemoryAccess(i));
        }

        return batchBalances;
    }

    /**
     * @dev See {IERC1155-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC1155-isApprovedForAll}.
     */
    function isApprovedForAll(address account, address operator) public view virtual returns (bool) {
        return _operatorApprovals[account][operator];
    }

    /**
     * @dev See {IERC1155-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) public virtual {
        address sender = _msgSender();
        if (from != sender && !isApprovedForAll(from, sender)) {
            revert ERC1155MissingApprovalForAll(sender, from);
        }
        _safeTransferFrom(from, to, id, value, data);
    }

    /**
     * @dev See {IERC1155-safeBatchTransferFrom}.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) public virtual {
        address sender = _msgSender();
        if (from != sender && !isApprovedForAll(from, sender)) {
            revert ERC1155MissingApprovalForAll(sender, from);
        }
        _safeBatchTransferFrom(from, to, ids, values, data);
    }

    /**
     * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from`
     * (or `to`) is the zero address.
     *
     * Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received}
     *   or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value.
     * - `ids` and `values` must have the same length.
     *
     * NOTE: The ERC-1155 acceptance check is not performed in this function. See {_updateWithAcceptanceCheck} instead.
     */
    function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual {
        if (ids.length != values.length) {
            revert ERC1155InvalidArrayLength(ids.length, values.length);
        }

        address operator = _msgSender();

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids.unsafeMemoryAccess(i);
            uint256 value = values.unsafeMemoryAccess(i);

            if (from != address(0)) {
                uint256 fromBalance = _balances[id][from];
                if (fromBalance < value) {
                    revert ERC1155InsufficientBalance(from, fromBalance, value, id);
                }
                unchecked {
                    // Overflow not possible: value <= fromBalance
                    _balances[id][from] = fromBalance - value;
                }
            }

            if (to != address(0)) {
                _balances[id][to] += value;
            }
        }

        if (ids.length == 1) {
            uint256 id = ids.unsafeMemoryAccess(0);
            uint256 value = values.unsafeMemoryAccess(0);
            emit TransferSingle(operator, from, to, id, value);
        } else {
            emit TransferBatch(operator, from, to, ids, values);
        }
    }

    /**
     * @dev Version of {_update} that performs the token acceptance check by calling
     * {IERC1155Receiver-onERC1155Received} or {IERC1155Receiver-onERC1155BatchReceived} on the receiver address if it
     * contains code (eg. is a smart contract at the moment of execution).
     *
     * IMPORTANT: Overriding this function is discouraged because it poses a reentrancy risk from the receiver. So any
     * update to the contract state after this function would break the check-effect-interaction pattern. Consider
     * overriding {_update} instead.
     */
    function _updateWithAcceptanceCheck(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) internal virtual {
        _update(from, to, ids, values);
        if (to != address(0)) {
            address operator = _msgSender();
            if (ids.length == 1) {
                uint256 id = ids.unsafeMemoryAccess(0);
                uint256 value = values.unsafeMemoryAccess(0);
                ERC1155Utils.checkOnERC1155Received(operator, from, to, id, value, data);
            } else {
                ERC1155Utils.checkOnERC1155BatchReceived(operator, from, to, ids, values, data);
            }
        }
    }

    /**
     * @dev Transfers a `value` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `from` must have a balance of tokens of type `id` of at least `value` amount.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) internal {
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
        if (from == address(0)) {
            revert ERC1155InvalidSender(address(0));
        }
        (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
        _updateWithAcceptanceCheck(from, to, ids, values, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     * - `ids` and `values` must have the same length.
     */
    function _safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) internal {
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
        if (from == address(0)) {
            revert ERC1155InvalidSender(address(0));
        }
        _updateWithAcceptanceCheck(from, to, ids, values, data);
    }

    /**
     * @dev Sets a new URI for all token types, by relying on the token type ID
     * substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the ERC].
     *
     * By this mechanism, any occurrence of the `\{id\}` substring in either the
     * URI or any of the values in the JSON file at said URI will be replaced by
     * clients with the token type ID.
     *
     * For example, the `https://token-cdn-domain/\{id\}.json` URI would be
     * interpreted by clients as
     * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
     * for token type ID 0x4cce0.
     *
     * See {uri}.
     *
     * Because these URIs cannot be meaningfully represented by the {URI} event,
     * this function emits no events.
     */
    function _setURI(string memory newuri) internal virtual {
        _uri = newuri;
    }

    /**
     * @dev Creates a `value` amount of tokens of type `id`, and assigns them to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(address to, uint256 id, uint256 value, bytes memory data) internal {
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
        (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
        _updateWithAcceptanceCheck(address(0), to, ids, values, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `values` must have the same length.
     * - `to` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal {
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
        _updateWithAcceptanceCheck(address(0), to, ids, values, data);
    }

    /**
     * @dev Destroys a `value` amount of tokens of type `id` from `from`
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `from` must have at least `value` amount of tokens of type `id`.
     */
    function _burn(address from, uint256 id, uint256 value) internal {
        if (from == address(0)) {
            revert ERC1155InvalidSender(address(0));
        }
        (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
        _updateWithAcceptanceCheck(from, address(0), ids, values, "");
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `from` must have at least `value` amount of tokens of type `id`.
     * - `ids` and `values` must have the same length.
     */
    function _burnBatch(address from, uint256[] memory ids, uint256[] memory values) internal {
        if (from == address(0)) {
            revert ERC1155InvalidSender(address(0));
        }
        _updateWithAcceptanceCheck(from, address(0), ids, values, "");
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the zero address.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        if (operator == address(0)) {
            revert ERC1155InvalidOperator(address(0));
        }
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Creates an array in memory with only one value for each of the elements provided.
     */
    function _asSingletonArrays(
        uint256 element1,
        uint256 element2
    ) private pure returns (uint256[] memory array1, uint256[] memory array2) {
        assembly ("memory-safe") {
            // Load the free memory pointer
            array1 := mload(0x40)
            // Set array length to 1
            mstore(array1, 1)
            // Store the single element at the next word after the length (where content starts)
            mstore(add(array1, 0x20), element1)

            // Repeat for next array locating it right after the first array
            array2 := add(array1, 0x40)
            mstore(array2, 1)
            mstore(add(array2, 0x20), element2)

            // Update the free memory pointer by pointing after the second array
            mstore(0x40, add(array2, 0x40))
        }
    }
}
"
    },
    "@openzeppelin/contracts/utils/math/Math.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Return the 512-bit 

Tags:
ERC20, ERC1155, ERC165, Multisig, Mintable, Non-Fungible, Swap, Upgradeable, Multi-Signature, Factory|addr:0xb501ed769c8bd6ca7af0be5e14730f1c77eb9bde|verified:true|block:23492331|tx:0x9330ca160ea8717cad47b57ef1c82198b0b92f4e7136c0c546640c6392640142|first_check:1759476103

Submitted on: 2025-10-03 09:21:44

Comments

Log in to comment.

No comments yet.