EthereumEthereum
0xb4...3B3d
Kairos Eagle

Kairos Eagle

KEG

收藏品
大小
25
收藏品
所有者
25
100% 独特的所有者
此合同的源代码已经过验证!
合同元数据
编译器
0.8.18+commit.87f61d96
语言
Solidity
合同源代码
文件 1 的 36:AdminFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IOwnershipFacet} from "./interface/IOwnershipFacet.sol";
import {IAdminFacet} from "./interface/IAdminFacet.sol";

import {Ray} from "./DataStructure/Objects.sol";
import {SupplyPosition, Protocol} from "./DataStructure/Storage.sol";
import {protocolStorage, ONE} from "./DataStructure/Global.sol";
import {CallerIsNotOwner} from "./DataStructure/Errors.sol";
import {RayMath} from "./utils/RayMath.sol";

/// @notice admin-only setters for global protocol parameters
contract AdminFacet is IAdminFacet {
    using RayMath for Ray;

    /// @notice restrict a method access to the protocol owner only
    modifier onlyOwner() {
        // the admin/owner is the same account that can upgrade the protocol.
        address admin = IOwnershipFacet(address(this)).owner();
        if (msg.sender != admin) {
            revert CallerIsNotOwner(admin);
        }
        _;
    }

    /// @notice sets the time it takes to auction prices to fall to 0 for future loans
    /// @param newAuctionDuration number of seconds of the duration
    function setAuctionDuration(uint256 newAuctionDuration) external onlyOwner {
        protocolStorage().auction.duration = newAuctionDuration;
        emit NewAuctionDuration(newAuctionDuration);
    }

    /// @notice sets the factor applied to the loan to value setting initial price of auction for future loans
    /// @param newAuctionPriceFactor the new factor multiplied to the loan to value
    function setAuctionPriceFactor(Ray newAuctionPriceFactor) external onlyOwner {
        // see auction facet for the rationale of this check
        require(newAuctionPriceFactor.gte(ONE.mul(5).div(2)), "");
        protocolStorage().auction.priceFactor = newAuctionPriceFactor;
        emit NewAuctionPriceFactor(newAuctionPriceFactor);
    }

    /// @notice creates a new tranche at a new identifier for lenders to provide offers for
    /// @param newTranche the interest rate of the new tranche
    function createTranche(Ray newTranche) external onlyOwner returns (uint256 newTrancheId) {
        Protocol storage proto = protocolStorage();

        newTrancheId = proto.nbOfTranches++;
        proto.tranche[newTrancheId] = newTranche;

        emit NewTranche(newTranche, newTrancheId);
    }

    /* Both minimal offer cost and lower amount per offer lower bound are anti ddos mechanisms used to prevent the
    borrowers to spam the minting of supply positions that lenders would have no incentive to claim the corresponding
    dust funds from due to gas costs. The minimal offer cost mainly prevents this for claims after repayment, the amount
    per offer lower bound mainly prevents this for claims after liquidation. The governance setting those parameters
    effectively makes new erc20 available to use on the platform (they are disallowed otherwise). This should not
    be done for any fee-on-transfer token. */

    /// @notice updates the minimum amount to repay per used loan offer when borrowing a certain currency
    /// @param currency the erc20 on which a new minimum borrow cost will take effect
    /// @param newMinOfferCost the new minimum amount that will need to be repaid per loan offer used
    function setMinOfferCost(IERC20 currency, uint256 newMinOfferCost) external onlyOwner {
        protocolStorage().minOfferCost[currency] = newMinOfferCost;
        emit NewMininimumOfferCost(currency, newMinOfferCost);
    }

    /// @notice updates the borrow amount lower bound per offer for one currency
    /// @param currency the erc20 on which a new borrow amount lower bound is taking effect
    /// @param newLowerBound the new lower bound
    function setBorrowAmountPerOfferLowerBound(IERC20 currency, uint256 newLowerBound) external onlyOwner {
        protocolStorage().offerBorrowAmountLowerBound[currency] = newLowerBound;
        emit NewBorrowAmountPerOfferLowerBound(currency, newLowerBound);
    }
}
合同源代码
文件 2 的 36:AuctionFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IAuctionFacet} from "./interface/IAuctionFacet.sol";

import {BuyArg, NFToken, Ray} from "./DataStructure/Objects.sol";
import {Loan, Protocol, Provision, SupplyPosition} from "./DataStructure/Storage.sol";
import {RayMath} from "./utils/RayMath.sol";
import {Erc20CheckedTransfer} from "./utils/Erc20CheckedTransfer.sol";
import {SafeMint} from "./SupplyPositionLogic/SafeMint.sol";
import {protocolStorage, supplyPositionStorage, ONE, ZERO} from "./DataStructure/Global.sol";
// solhint-disable-next-line max-line-length
import {LoanAlreadyRepaid, CollateralIsNotLiquidableYet, PriceOverMaximum} from "./DataStructure/Errors.sol";

/// @notice handles sale of collaterals being liquidated, following a dutch auction starting at repayment date
contract AuctionFacet is IAuctionFacet, SafeMint {
    using RayMath for Ray;
    using RayMath for uint256;
    using Erc20CheckedTransfer for IERC20;

    /// @notice buy one or multiple NFTs in liquidation
    /// @param args arguments on what and how to buy
    function buy(BuyArg[] memory args) external {
        for (uint256 i = 0; i < args.length; i++) {
            useLoan(args[i]);
        }
    }

    /// @notice gets the price to buy the underlying collateral of the loan
    /// @param loanId identifier of the loan
    /// @return price computed price
    function price(uint256 loanId) public view returns (uint256) {
        Loan storage loan = protocolStorage().loan[loanId];
        uint256 loanEndDate = loan.endDate;
        checkLoanStatus(loanId);
        uint256 timeSinceLiquidable = block.timestamp - loanEndDate;

        /* the decreasing factor controls the evolution of the price from its initial value to 0 (and staying at 0)
        over the course of the auction duration */
        Ray decreasingFactor = timeSinceLiquidable >= loan.auction.duration
            ? ZERO
            : ONE.sub(timeSinceLiquidable.div(loan.auction.duration));

        uint256 estimatedValue = loan.lent.div(loan.shareLent);

        /* by mutliplying the estimated price by some factor and slowly decreasing this price over time we aim to
        make sure a liquidator will buy the NFT at fair market price. */
        return estimatedValue.mul(loan.auction.priceFactor).mul(decreasingFactor);
    }

    /// @notice handles buying one NFT
    /// @param arg arguments on what and how to buy
    function useLoan(BuyArg memory arg) internal {
        Loan storage loan = protocolStorage().loan[arg.loanId];

        uint256 toPay = price(arg.loanId); // includes checks on loan status

        if (toPay > arg.maxPrice) {
            /* in case of a reorg, the transaction can be included at a block timestamp date earlier than actual
            transaction signature date, resulting in a price unexpectedly high. */
            revert PriceOverMaximum(arg.maxPrice, toPay);
        }

        /* store as liquidated and paid before transfers to avoid malicious reentrency, following
        checks-effects-interactions pattern */
        loan.payment.liquidated = true;
        loan.payment.paid = toPay;
        loan.assetLent.checkedTransferFrom(msg.sender, address(this), toPay);
        loan.collateral.implem.safeTransferFrom(address(this), arg.to, loan.collateral.id);

        emit Buy(arg.loanId, abi.encode(arg));
    }

    /// @notice checks that loan is liquidable, revert if not
    /// @param loanId identifier of the loan
    function checkLoanStatus(uint256 loanId) internal view {
        Loan storage loan = protocolStorage().loan[loanId];

        if (block.timestamp <= loan.endDate) {
            revert CollateralIsNotLiquidableYet(loan.endDate, loanId);
        }
        if (loan.payment.paid != 0 || loan.payment.liquidated) {
            revert LoanAlreadyRepaid(loanId);
        }
    }
}
合同源代码
文件 3 的 36:BorrowCheckers.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

import {IBorrowCheckers} from "../interface/IBorrowCheckers.sol";

import {Signature} from "../Signature.sol";
import {NFTokenUtils} from "../utils/NFTokenUtils.sol";
import {Offer, OfferArg, NFToken} from "../../src/DataStructure/Objects.sol";
import {Protocol} from "../../src/DataStructure/Storage.sol";
import {protocolStorage} from "../../src/DataStructure/Global.sol";
// solhint-disable-next-line max-line-length
import {BadCollateral, OfferHasExpired, RequestedAmountTooHigh, RequestedAmountIsUnderMinimum, CurrencyNotSupported, InvalidTranche} from "../../src/DataStructure/Errors.sol";

/// @notice handles checks to verify validity of a loan request
abstract contract BorrowCheckers is IBorrowCheckers, Signature {
    using NFTokenUtils for NFToken;

    /// @notice checks arguments validity for usage of one Offer
    /// @param arg arguments for the Offer
    /// @return signer computed signer of `arg.signature` according to `arg.offer`
    function checkOfferArg(OfferArg memory arg) internal view returns (address signer) {
        Protocol storage proto = protocolStorage();

        /* it is statistically impossible to forge a signature that would lead to finding a signer that does not aggrees
        to the signed loan offer and that usage wouldn't revert due to the absence of approved funds to mobilize. This
        is how we know the signer address can't be the wrong one without leading to a revert. */
        signer = ECDSA.recover(offerDigest(arg.offer), arg.signature);

        /* we use a lower bound, I.e the actual amount must be strictly higher that this bound as a way to prevent a 0 
        amount to be used even in the case of an uninitialized parameter for a given erc20. This bound set by governance
        is used as an anti-ddos measure to prevent borrowers to spam the creation of supply positions not worth to claim
        by lenders from a gas cost perspective after a liquidation. more info in docs */
        uint256 amountLowerBound = proto.offerBorrowAmountLowerBound[arg.offer.assetToLend];

        // only erc20s for which the governance has set minimum thresholds are safe to use
        if (amountLowerBound == 0 || proto.minOfferCost[arg.offer.assetToLend] == 0) {
            revert CurrencyNotSupported(arg.offer.assetToLend);
        }

        if (!(arg.amount > amountLowerBound)) {
            revert RequestedAmountIsUnderMinimum(arg.offer, arg.amount, amountLowerBound);
        }
        /* the offer expiration date is meant to be used by lenders as a way to manage the evolution of market
        conditions */
        if (block.timestamp > arg.offer.expirationDate) {
            revert OfferHasExpired(arg.offer, arg.offer.expirationDate);
        }

        /* as tranches can't be deactivated, checking the number of tranches allows us to deduce if the tranche id is
        valid */
        if (arg.offer.tranche >= proto.nbOfTranches) {
            revert InvalidTranche(proto.nbOfTranches);
        }
    }

    /// @notice checks collateral validity regarding the offer
    /// @param offer loan offer which validity should be checked for the provided collateral
    /// @param providedNft nft sent to be used as collateral
    function checkCollateral(Offer memory offer, NFToken memory providedNft) internal pure {
        // we check the lender indeed approves the usage of its offer for the collateral used
        if (!offer.collateral.eq(providedNft)) {
            revert BadCollateral(offer, providedNft);
        }
    }
}
合同源代码
文件 4 的 36:BorrowFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import {IBorrowFacet} from "./interface/IBorrowFacet.sol";

import {BorrowHandlers} from "./BorrowLogic/BorrowHandlers.sol";
import {BorrowArg, NFToken, Offer, OfferArg} from "./DataStructure/Objects.sol";
import {Signature} from "./Signature.sol";

/// @notice public facing methods for borrowing
/// @dev contract handles all borrowing logic through inheritance
contract BorrowFacet is IBorrowFacet, BorrowHandlers {
    /// @notice borrow using sent NFT as collateral without needing approval through transfer callback
    /// @param from account that owned the NFT before transfer
    /// @param tokenId token identifier of the NFT sent according to the NFT implementation contract
    /// @param data abi encoded arguments for the loan
    /// @return selector `this.onERC721Received.selector` ERC721-compliant response, signaling compatibility
    /// @dev param data must be of format OfferArg[]
    function onERC721Received(
        address, // operator
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4) {
        OfferArg[] memory args = abi.decode(data, (OfferArg[]));

        // `from` will be considered the borrower, and the NFT will be transferred to the Kairos contract
        // the `operator` that called `safeTransferFrom` is ignored
        useCollateral(args, from, NFToken({implem: IERC721(msg.sender), id: tokenId}));

        return this.onERC721Received.selector;
    }

    /// @notice take loans, take ownership of NFTs specified as collateral, sends borrowed money to caller
    /// @param args list of arguments specifying at which terms each collateral should be used
    function borrow(BorrowArg[] calldata args) external {
        for (uint256 i = 0; i < args.length; i++) {
            args[i].nft.implem.transferFrom(msg.sender, address(this), args[i].nft.id);
            useCollateral(args[i].args, msg.sender, args[i].nft);
        }
    }
}
合同源代码
文件 5 的 36:BorrowHandlers.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IBorrowHandlers} from "../interface/IBorrowHandlers.sol";

import {BorrowCheckers} from "./BorrowCheckers.sol";
import {CollateralState, NFToken, OfferArg, Ray} from "../DataStructure/Objects.sol";
import {Loan, Payment, Protocol, Provision, Auction} from "../DataStructure/Storage.sol";
import {ONE, protocolStorage, supplyPositionStorage} from "../DataStructure/Global.sol";
import {RayMath} from "../utils/RayMath.sol";
import {Erc20CheckedTransfer} from "../utils/Erc20CheckedTransfer.sol";
import {SafeMint} from "../SupplyPositionLogic/SafeMint.sol";
// solhint-disable-next-line max-line-length
import {RequestedAmountTooHigh, UnsafeAmountLent, MultipleOffersUsed, ShareMatchedIsTooLow} from "../DataStructure/Errors.sol";

/// @notice handles usage of entities to borrow with
abstract contract BorrowHandlers is IBorrowHandlers, BorrowCheckers, SafeMint {
    using RayMath for uint256;
    using RayMath for Ray;
    using Erc20CheckedTransfer for IERC20;

    Ray private immutable minShareLent;

    constructor() {
        /* see testWorstCaseEstimatedValue() in RayMath.t.sol for the test showing worst case considered values
        in the return value calculation of AuctionFacet.sol's price(uint256 loanId) method */
        minShareLent = ONE.div(100_000_000);
    }

    /// @notice handles usage of a loan offer to borrow from
    /// @param arg arguments for the usage of this offer
    /// @param collatState tracked state of the matching of the collateral
    /// @return collateralState updated `collatState` after usage of the offer
    function useOffer(
        OfferArg memory arg,
        CollateralState memory collatState
    ) internal view returns (CollateralState memory, address /* signer */) {
        address signer = checkOfferArg(arg);
        Ray shareMatched;

        checkCollateral(arg.offer, collatState.nft);

        // we keep track of the share of the maximum value (`loanToValue`) proposed by an offer used by the borrower.
        shareMatched = arg.amount.div(arg.offer.loanToValue);

        // a 0 share or too low can lead to DOS, cf https://github.com/sherlock-audit/2023-02-kairos-judging/issues/76
        if (shareMatched.lt(minShareLent)) {
            revert ShareMatchedIsTooLow(arg.offer, arg.amount);
        }

        collatState.matched = collatState.matched.add(shareMatched);

        /* we consider that lenders are acquiring shares of the NFT used as collateral by lending the amount
        corresponding to shareMatched. We check this process is not ditributing more shares than the full NFT value. */
        if (collatState.matched.gt(ONE)) {
            revert RequestedAmountTooHigh(
                arg.amount,
                arg.offer.loanToValue.mul(ONE.sub(collatState.matched.sub(shareMatched))),
                arg.offer
            );
        }

        return (collatState, signer);
    }

    /// @notice handles usage of one collateral to back a loan request
    /// @param args arguments for usage of one or multiple loan offers
    /// @param from borrower for this loan
    /// @param nft collateral to use
    /// @return loan the loan created backed by provided collateral
    function useCollateral(
        OfferArg[] memory args,
        address from,
        NFToken memory nft
    ) internal returns (Loan memory loan) {
        address signer;
        CollateralState memory collatState = initializedCollateralState(args[0], from, nft);

        /* following the sherlock audit, we found some possible manipulations in multi offers loans. This condition is
        change-minimized prevention to this, keeping the code as close to the reviewed version as possible. An optimized
        Kairos Loan v2 will soon be published. */
        if (args.length > 1) {
            revert MultipleOffersUsed();
        }

        (collatState, signer) = useOffer(args[0], collatState);
        uint256 lent = args[0].amount;

        // cf RepayFacet for the rationale of this check. We prevent repaying being impossible due to an overflow in the
        // interests to repay calculation.
        if (lent > 1e40) {
            revert UnsafeAmountLent(lent);
        }
        loan = initializedLoan(collatState, from, nft, lent);
        protocolStorage().loan[collatState.loanId] = loan;

        // transferring the borrowed funds from the lender to the borrower
        collatState.assetLent.checkedTransferFrom(signer, collatState.from, lent);

        /* issuing supply position NFT to the signer of the loan offer with metadatas
        The only position of the loan is not minted in useOffer but in the end of this functions as a way to better
        follow the checks-effects-interactions pattern as it includes an external call, to prevent unforseen
        consequences of a reentrency. */
        safeMint(signer, Provision({amount: lent, share: collatState.matched, loanId: collatState.loanId}));

        emit Borrow(collatState.loanId, abi.encode(loan));
    }

    /// @notice initializes the collateral state memory struct used to keep track of the collateralization and other
    ///     health checks variables during the issuance of a loan
    /// @param firstOfferArg the first struct of arguments for an offer among potentially multiple used loan offers
    /// @param from I.e borrower
    /// @param nft - used as collateral
    /// @return collatState the initialized collateral state struct
    function initializedCollateralState(
        OfferArg memory firstOfferArg,
        address from,
        NFToken memory nft
    ) internal returns (CollateralState memory) {
        return
            CollateralState({
                matched: Ray.wrap(0),
                assetLent: firstOfferArg.offer.assetToLend,
                tranche: firstOfferArg.offer.tranche,
                minOfferDuration: firstOfferArg.offer.duration,
                minOfferLoanToValue: firstOfferArg.offer.loanToValue,
                maxOfferLoanToValue: firstOfferArg.offer.loanToValue,
                from: from,
                nft: nft,
                loanId: ++protocolStorage().nbOfLoans // returns incremented value (also increments in storage)
            });
    }

    /// @notice initializes the loan struct representing borrowed funds from one NFT collateral, will be stored
    /// @param collatState contains info on share of the collateral value used by the borrower
    /// @param nft - used as collateral
    /// @param lent amount lent/borrowed
    /// @return loan tne initialized loan to store
    function initializedLoan(
        CollateralState memory collatState,
        address from,
        NFToken memory nft,
        uint256 lent
    ) internal view returns (Loan memory) {
        Protocol storage proto = protocolStorage();

        /* the shortest offered duration determines the max date of repayment to make sure all loan offer terms are
        respected */
        uint256 endDate = block.timestamp + collatState.minOfferDuration;
        Payment memory notPaid; // not paid as it corresponds to the meaning of the uninitialized struct

        /* the minimum interests amount to repay is used as anti ddos mechanism to prevent borrowers to produce lots of
        dust supply positions that the lenders will have to pay gas to claim. as each position can be used to claim
        funds separetely and induce a gas cost. With a design approach similar to the auction parameters setting,
        this minimal cost is set at borrow time to avoid bad surprises arising from governance setting new parameters
        during the loan life. cf docs for more details. */
        notPaid.minInterestsToRepay = proto.minOfferCost[collatState.assetLent];

        return
            Loan({
                assetLent: collatState.assetLent,
                lent: lent,
                shareLent: collatState.matched,
                startDate: block.timestamp,
                endDate: endDate,
                /* auction parameters are copied from protocol parameters to the loan storage as a way to prevent
                a governance-initiated change of terms to modify the terms a borrower chose to accept or change the
                price of an NFT being sold abruptly during the course of an auction. */
                auction: Auction({duration: proto.auction.duration, priceFactor: proto.auction.priceFactor}),
                /* the interest rate is stored as a value instead of the tranche id as a precaution in case of a change
                in the interest rate mechanisms due to contract upgrade */
                interestPerSecond: proto.tranche[collatState.tranche],
                borrower: from,
                collateral: nft,
                payment: notPaid
            });
    }
}
合同源代码
文件 6 的 36:ClaimFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IClaimFacet} from "./interface/IClaimFacet.sol";
import {BorrowerAlreadyClaimed, LoanNotRepaidOrLiquidatedYet, NotBorrowerOfTheLoan} from "./DataStructure/Errors.sol";
import {ERC721CallerIsNotOwner} from "./DataStructure/ERC721Errors.sol";
import {Loan, Protocol, Provision, SupplyPosition} from "./DataStructure/Storage.sol";
import {ONE, protocolStorage, supplyPositionStorage} from "./DataStructure/Global.sol";
import {Ray} from "./DataStructure/Objects.sol";
import {RayMath} from "./utils/RayMath.sol";
import {Erc20CheckedTransfer} from "./utils/Erc20CheckedTransfer.sol";
import {SafeMint} from "./SupplyPositionLogic/SafeMint.sol";

/// @notice claims supplier and borrower rights on loans or supply positions
contract ClaimFacet is IClaimFacet, SafeMint {
    using RayMath for Ray;
    using RayMath for uint256;
    using Erc20CheckedTransfer for IERC20;

    /// @notice claims principal plus interests or liquidation share due as a supplier
    /// @param positionIds identifiers of one or multiple supply position to burn
    /// @return sent amount sent
    function claim(uint256[] calldata positionIds) external returns (uint256 sent) {
        Protocol storage proto = protocolStorage();
        SupplyPosition storage sp = supplyPositionStorage();
        Loan storage loan;
        Provision storage provision;
        uint256 loanId;
        uint256 sentTemp;

        for (uint256 i = 0; i < positionIds.length; i++) {
            if (sp.owner[positionIds[i]] != msg.sender) {
                revert ERC721CallerIsNotOwner();
            }
            _burn(positionIds[i]);
            provision = sp.provision[positionIds[i]];
            loanId = provision.loanId;
            loan = proto.loan[loanId];

            if (loan.payment.liquidated) {
                sentTemp = sendShareOfSaleAsSupplier(loan, provision);
            } else {
                if (loan.payment.paid == 0) {
                    revert LoanNotRepaidOrLiquidatedYet(loanId);
                }
                sentTemp = sendInterests(loan, provision);
            }
            emit Claim(msg.sender, sentTemp, loanId);
            sent += sentTemp;
        }
    }

    /// @notice claims share of liquidation due to a borrower who's collateral has been sold
    /// @param loanIds loan identifiers of one or multiple loans where the borrower wants to claim liquidation share
    /// @return sent amount sent
    function claimAsBorrower(uint256[] calldata loanIds) external returns (uint256 sent) {
        Protocol storage proto = protocolStorage();
        Loan storage loan;
        uint256 sentTemp;
        uint256 loanId;

        for (uint256 i = 0; i < loanIds.length; i++) {
            loanId = loanIds[i];
            loan = proto.loan[loanId];
            if (loan.borrower != msg.sender) {
                revert NotBorrowerOfTheLoan(loanId);
            }
            if (loan.payment.borrowerClaimed) {
                revert BorrowerAlreadyClaimed(loanId);
            }
            if (loan.payment.liquidated) {
                loan.payment.borrowerClaimed = true;
                // 1 - shareLent = share belonging to the borrower (not used as collateral)
                sentTemp = loan.payment.paid.mul(ONE.sub(loan.shareLent));
            } else {
                revert LoanNotRepaidOrLiquidatedYet(loanId);
            }
            if (sentTemp > 0) {
                /* the function may be called to store that the borrower claimed its due, but if this due is of 0 there
                is no point in emitting a transfer and claim event */
                loan.assetLent.checkedTransfer(msg.sender, sentTemp);
                sent += sentTemp;
                emit Claim(msg.sender, sentTemp, loanId);
                // sentTemp is reassigned or the execution reverts on next loop
            }
        }
    }

    /// @notice sends principal plus interests of the loan to `msg.sender`
    /// @param loan - to calculate amount from
    /// @param provision liquidity provision for this loan
    /// @return sent amount sent
    function sendInterests(Loan storage loan, Provision storage provision) internal returns (uint256 sent) {
        uint256 interests = loan.payment.paid - loan.lent;
        if (interests == loan.payment.minInterestsToRepay) {
            // this is the case if the loan is repaid shortly after issuance
            // each lender gets its minimal interest, as an anti ddos measure to spam offer
            sent = provision.amount + interests;
        } else {
            /* provision.amount / lent = share of the interests belonging to the lender. The parenthesis make the
            calculus in the order that maximizes precison */
            sent = provision.amount + (interests * (provision.amount)) / loan.lent;
        }
        loan.assetLent.checkedTransfer(msg.sender, sent);
    }

    /// @notice sends liquidation share due to `msg.sender` as a supplier
    /// @param loan - from which the collateral were liquidated
    /// @param provision liquidity provisioned by this loan by the supplier
    /// @return sent amount sent
    function sendShareOfSaleAsSupplier(Loan storage loan, Provision storage provision) internal returns (uint256 sent) {
        // in the case of a liqudidation, provision.share is considered the share of the NFT acquired by the lender
        sent = loan.payment.paid.mul(provision.share);
        loan.assetLent.checkedTransfer(msg.sender, sent);
    }
}
合同源代码
文件 7 的 36:ContractsCreator.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {OwnershipFacet} from "diamond/contracts/facets/OwnershipFacet.sol";
import {DiamondCutFacet} from "diamond/contracts/facets/DiamondCutFacet.sol";
import {IDiamond} from "diamond/contracts/interfaces/IDiamond.sol";
import {IDiamondCut} from "diamond/contracts/interfaces/IDiamondCut.sol";
import {DiamondLoupeFacet} from "diamond/contracts/facets/DiamondLoupeFacet.sol";

import {AdminFacet} from "./AdminFacet.sol";
import {AuctionFacet} from "./AuctionFacet.sol";
import {BorrowFacet} from "./BorrowFacet.sol";
import {ClaimFacet} from "./ClaimFacet.sol";
import {Initializer} from "./Initializer.sol";
import {ProtocolFacet} from "./ProtocolFacet.sol";
import {RepayFacet} from "./RepayFacet.sol";
import {SupplyPositionFacet} from "./SupplyPositionFacet.sol";
/* solhint-disable-next-line max-line-length */
import {adminFS, auctionFS, claimFS, borrowFS, cutFS, loupeFS, protoFS, ownershipFS, repayFS, supplyPositionFS} from "./utils/FuncSelectors.h.sol";

/// @notice handles uinitialized deployment of all contracts of the protocol and exposes facet cuts
/// @dev for production, the 3 contracts imported from diamonds don't have to be redeployed as they are already
///      existing on most chain, modify deploy script accordingly
contract ContractsCreator {
    Initializer internal initializer;
    DiamondCutFacet internal cut;
    OwnershipFacet internal ownership;
    DiamondLoupeFacet internal loupe;
    AdminFacet internal admin;
    BorrowFacet internal borrow;
    SupplyPositionFacet internal supplyPosition;
    ProtocolFacet internal protocol;
    RepayFacet internal repay;
    AuctionFacet internal auction;
    ClaimFacet internal claim;

    /// @notice deploys all contracts uninitialized
    function createContracts() internal {
        admin = new AdminFacet();
        cut = new DiamondCutFacet();
        loupe = new DiamondLoupeFacet();
        ownership = new OwnershipFacet();
        repay = new RepayFacet();
        borrow = new BorrowFacet();
        supplyPosition = new SupplyPositionFacet();
        protocol = new ProtocolFacet();
        initializer = new Initializer();
        auction = new AuctionFacet();
        claim = new ClaimFacet();
    }

    /// @notice get all facet cuts to add to add to a diamond to create kairos
    /// @return facetCuts the list of facet cuts
    /* solhint-disable-next-line function-max-lines */
    function getFacetCuts() internal view returns (IDiamondCut.FacetCut[] memory) {
        IDiamondCut.FacetCut[] memory facetCuts = new IDiamondCut.FacetCut[](10);

        facetCuts[0] = getAddFacetCut(address(loupe), loupeFS());
        facetCuts[1] = getAddFacetCut(address(ownership), ownershipFS());
        facetCuts[2] = getAddFacetCut(address(cut), cutFS());
        facetCuts[3] = getAddFacetCut(address(borrow), borrowFS());
        facetCuts[4] = getAddFacetCut(address(supplyPosition), supplyPositionFS());
        facetCuts[5] = getAddFacetCut(address(protocol), protoFS());
        facetCuts[6] = getAddFacetCut(address(repay), repayFS());
        facetCuts[7] = getAddFacetCut(address(auction), auctionFS());
        facetCuts[8] = getAddFacetCut(address(claim), claimFS());
        facetCuts[9] = getAddFacetCut(address(admin), adminFS());

        return facetCuts;
    }

    function getAddFacetCut(
        address facet,
        bytes4[] memory selectors
    ) internal pure returns (IDiamondCut.FacetCut memory) {
        return
            IDiamond.FacetCut({facetAddress: facet, action: IDiamond.FacetCutAction.Add, functionSelectors: selectors});
    }

    function getUpgradeFacetCut(
        address facet,
        bytes4[] memory selectors
    ) internal pure returns (IDiamondCut.FacetCut memory) {
        return
            IDiamond.FacetCut({
                facetAddress: facet,
                action: IDiamond.FacetCutAction.Replace,
                functionSelectors: selectors
            });
    }

    function getRemoveFacetCut(bytes4[] memory selectors) internal pure returns (IDiamondCut.FacetCut memory) {
        return
            IDiamond.FacetCut({
                facetAddress: address(0),
                action: IDiamond.FacetCutAction.Remove,
                functionSelectors: selectors
            });
    }
}
合同源代码
文件 8 的 36:DeployEagle.s.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

//                            .a@@@@@#########@@@@a.
//                        .a@@######@@@mm@@mm######@@@a.
//                   .a####@@@@@@@@@@@@@@@@@@@mm@@##@@v;%%,.
//                .a###v@@@@@@@@vvvvvvvvvvvvvv@@@@#@v;%%%vv%%,
//             .a##vv@@@@@@@@vv%%%%;S,  .S;%%vv@@#v;%%'/%vvvv%;
//           .a##@v@@@@@vv%%vvvvvv%%;SssS;%%vvvv@v;%%./%vvvvvv%;
//         ,a##vv@@@vv%%%@@@@@@@@@@@@mmmmmmmmmvv;%%%%vvvvvvvvv%;
//         .a##@@@@@@@@@@@@@@@@@@@@@@@mmmmmvv;%%%%%vvvvvvvvvvv%;
//        ###vv@@@v##@v@@@@@@@@@@mmv;%;%;%;%;%;%;%;%;%;%;%,%vv%'
//       a#vv@@@@v##v@@@@@@@@###@@@@@%v%v%v%v%v%v%v%      ;%%;'
//      ',a@@@@@@@v@@@@@@@@v###v@@@nvnvnvnvnvnvnvnv'     .%;'
//      a###@@@@@@@###v@@@v##v@@@mnmnmnmnmnmnmnmnmn.     ;'
//     ,###vv@@@@v##v@@@@@@v@@@@v##v@@@@@v###v@@@##@.
//     ###vv@@@@@@v@@###v@@@@@@@@v@@@@@@v##v@@@v###v@@.
//    a@vv@@@@@@@@@v##v@@@@@@@@@@@@@@;@@@v@@@@v##v@@@@@@a
//   ',@@@@@@;@@@@@@v@@@@@@@@@@@@@@@;%@@@@@@@@@v@@@@;@@@@@a
//  .@@@@@@;%@@;@@@@@@@;;@@@@@;@@@@;%%;@@@@@;@@@@;@@@;@@@@@@.
// ,a@@@;vv;@%;@@@@@;%%v%;@@@;@@@;%vv%%;@@@;%;@@;%@@@;%;@@;%@@a
//   .@@@@@@;%@@;@@@@@@@;;@@@@@;@@@@;%%;@@@@@;@@@@;@@@;@@@@@@.
// ,a@@@;vv;@%;@@@@@;%%v%;@@@;@@@;%vv%%;@@@;%;@@;%@@@;%;@@;%@@a
//  a@;vv;%%%;@@;%%;vvv;%%@@;%;@;%vvv;%;@@;%%%;@;%;@;%%%@@;%%;.`
// ;@;%;vvv;%;@;%%;vv;%%%%v;%%%;%vv;%%v;@@;%vvv;%;%;%;%%%;%%%%;.%,
// %%%;vv;%;vv;%%%v;%%%%;vvv;%%%v;%%%;vvv;%;vv;%%%%%;vv;%%%;vvv;.%%%,
// ;vvv;%%;vv;%%;vv;%%%;vv;%%%;vvv;%;vv;%;vv;%;%%%;vv;%%%%;vv;%%.%v;v%
// vv;%;vvv;%;vv;;%%%%%%;%%%%;vv;%%%%;%%%%;%%;%%;%%;vv;%%%%;%%%;v.%vv;
// ;%%%%;%%%%%;%%%%%;%%%%;%%%%;%%%;%%%;%%%%%%%;%%%%%;%%%;%%%%;%%;.%%;%

// &&&&@7   :B@@@@P.     5@@@@@@B       Y@&&@G  ?B&@@@@@@@@@@&#Y.       :!YGB#BG5?~       ^5#&@@@@@@@&7
// @@@@@7  ^#@@@@5.     !@@@@@@@@?      Y@@@@B  ^^#@@@@@##&@@@@@P     :Y&@@@@@@@@@@BJ.   ^&@@@@@####B!
// @@@@@7 ~&@@@@?      .#@@@&&@@@&:     Y@@@@B    #@@@@5 ..B@@@@#     5@GPGB@@@@@@@@@#~  ?@@@@@J.
// @@@@@7~&@@@&~       Y@@@@J?@@@@5     Y@@@@B   .#@@@@5   P@@@@#     .!^!YB@@@@@@@@@@#: :B@@@@&BY!:
// @@@@@7Y@@@@#:      ~@@@@#. #@@@@~    Y@@@@B   .#@@@@5~YP&@@@#7  :~JPB@@@@@@@@@@@@@@@~  .?G&@@@@@&BJ:
// @@@@@7 5@@@@#^     G@@@@G77G@@@@#7!. Y@@@@B   .#@@@@5.B@@@@G.  !&@@@@@@@@@@@@@@@@@@#:     .~JG@@@@@B
// @@@@@7  5@@@@&~   ?@@@@@@@@@@@@@@@?  Y@@@@B   .#@@@@5 .B@@@@Y   ^Y#@@@@@@@@@@@@@@@#~   .:....:G@@@@&
// @@@@@7   Y@@@@&! :&@@@@5!!!~Y@@@@&:  Y@@@@B   .#@@@@5  :B@@@@Y    ~#@@@@@@@@@@@@BJ.   .#&&&&&&@@@@@5
// ####&7    J&&&&#^Y&&&&B.     G&##&J  J&##&G   .G###&Y   ^B&&&&?    .~7JJYJPBG5?~      .B&&&&&&&#BP!

// solhint-disable no-console
import {console} from "forge-std/console.sol";
import {Script} from "forge-std/Script.sol";
import {Diamond, DiamondArgs} from "diamond/contracts/Diamond.sol";
import {DiamondCutFacet} from "diamond/contracts/facets/DiamondCutFacet.sol";
import {OwnershipFacet} from "diamond/contracts/facets/OwnershipFacet.sol";
import {DiamondLoupeFacet} from "diamond/contracts/facets/DiamondLoupeFacet.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDiamondCut} from "diamond/contracts/interfaces/IDiamondCut.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {LibDiamond} from "diamond/contracts/libraries/LibDiamond.sol";
import {IERC173} from "diamond/contracts/interfaces/IERC173.sol";
import {IERC165} from "diamond/contracts/interfaces/IERC165.sol";
import {IDiamondLoupe} from "diamond/contracts/interfaces/IDiamondLoupe.sol";

import {DiamondERC721} from "../SupplyPositionLogic/DiamondERC721.sol";
import {supplyPositionStorage} from "../DataStructure/Global.sol";
import {SupplyPosition} from "../DataStructure/Storage.sol";
import {ContractsCreator} from "../ContractsCreator.sol";
import {KairosEagleFacet} from "./KairosEagle.sol";
import {loupeFS, ownershipFS, cutFS, getSelector} from "../utils/FuncSelectors.h.sol";

contract KairosEagle is Diamond {
    /* solhint-disable-next-line no-empty-blocks */
    constructor(IDiamondCut.FacetCut[] memory cuts, DiamondArgs memory _args) Diamond(cuts, _args) {}
}

contract EagleInitializer {
    function init() external {
        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
        ds.supportedInterfaces[type(IERC165).interfaceId] = true;
        ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true;
        ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true;
        ds.supportedInterfaces[type(IERC173).interfaceId] = true;
        ds.supportedInterfaces[type(IERC721).interfaceId] = true;
        SupplyPosition storage sp = supplyPositionStorage();
        sp.name = "Kairos Eagle";
        sp.symbol = "KEG";
    }
}

contract DeployEagle is Script, ContractsCreator {
    KairosEagleFacet internal eagle;
    EagleInitializer internal eagleInitializer;

    function run() public {
        uint256 privateKey = vm.envUint("DEPLOYER_KEY");

        vm.startBroadcast(privateKey);
        cut = DiamondCutFacet(0xc5126Eb24430d459Cd810B882C3AD286D380B6bD);
        loupe = DiamondLoupeFacet(0x47Ae465f27c69659e7D5012c4e2a6732A8aA8370);
        ownership = OwnershipFacet(0xC433107B136b6ea1Abb1b99E0A0a39459687599c);
        eagle = new KairosEagleFacet(IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));
        eagleInitializer = new EagleInitializer();

        DiamondArgs memory args = DiamondArgs({
            owner: vm.addr(privateKey),
            init: address(eagleInitializer),
            initCalldata: abi.encodeWithSelector(eagleInitializer.init.selector)
        });

        new KairosEagle(eagleFacetCuts(), args);
        console.logBytes(abi.encode(eagleFacetCuts(), args));
    }

    function eagleFacetCuts() internal view returns (IDiamondCut.FacetCut[] memory) {
        IDiamondCut.FacetCut[] memory facetCuts = new IDiamondCut.FacetCut[](4);

        facetCuts[0] = getAddFacetCut(address(loupe), loupeFS());
        facetCuts[1] = getAddFacetCut(address(ownership), ownershipFS());
        facetCuts[2] = getAddFacetCut(address(cut), cutFS());
        facetCuts[3] = getAddFacetCut(address(eagle), eagleFS());

        return facetCuts;
    }

    function eagleFS() internal pure returns (bytes4[] memory) {
        bytes4[] memory functionSelectors = new bytes4[](18);

        functionSelectors[0] = IERC721.balanceOf.selector;
        functionSelectors[1] = IERC721.ownerOf.selector;
        functionSelectors[2] = DiamondERC721.name.selector;
        functionSelectors[3] = DiamondERC721.symbol.selector;
        functionSelectors[4] = IERC721.approve.selector;
        functionSelectors[5] = IERC721.getApproved.selector;
        functionSelectors[6] = IERC721.setApprovalForAll.selector;
        functionSelectors[7] = IERC721.isApprovedForAll.selector;
        functionSelectors[8] = IERC721.transferFrom.selector;
        functionSelectors[9] = getSelector("safeTransferFrom(address,address,uint256)");
        functionSelectors[10] = getSelector("safeTransferFrom(address,address,uint256,bytes)");
        functionSelectors[11] = KairosEagleFacet.buy.selector;
        functionSelectors[12] = KairosEagleFacet.setRaffle.selector;
        functionSelectors[13] = KairosEagleFacet.withdrawFunds.selector;
        functionSelectors[14] = KairosEagleFacet.setBaseMetadataUri.selector;
        functionSelectors[15] = KairosEagleFacet.tokenURI.selector;
        functionSelectors[16] = KairosEagleFacet.totalSupply.selector;
        functionSelectors[17] = KairosEagleFacet.getHardCap.selector;

        return functionSelectors;
    }
}
合同源代码
文件 9 的 36:DiamondERC721.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {NFTUtils} from "./NFTUtils.sol";
import {SupplyPosition} from "../DataStructure/Storage.sol";
import {supplyPositionStorage} from "../DataStructure/Global.sol";
import {ERC721AddressZeroIsNotAValidOwner, ERC721ApprovalToCurrentOwner, ERC721CallerIsNotOwnerNorApproved, ERC721CallerIsNotOwnerNorApprovedForAll} from "../DataStructure/ERC721Errors.sol";

/// @title ERC721 Diamond Facet
/// @notice implements basic ERC721 for usage as a diamond facet
/// @dev based on OpenZeppelin's implementation
///     this is a minimalist implementation, notably missing are the
///     tokenURI, _baseURI, _beforeTokenTransfer and _afterTokenTransfer methods
/// @author Kairos protocol
abstract contract DiamondERC721 is IERC721, NFTUtils {
    using Address for address;

    error Unauthorized();

    // constructor equivalent is in the Initializer contract

    /// @dev don't use this method for inclusion in the facet function selectors
    ///     prefer the LibDiamond implementation for this method
    ///     it is included here for IERC721-compliance
    /* solhint-disable-next-line no-empty-blocks */
    function supportsInterface(bytes4 interfaceId) public view returns (bool) {}

    function emitTransfer(address from, address to, uint256 tokenId) internal virtual override {
        emit Transfer(from, to, tokenId);
    }

    function emitApproval(address owner, address approved, uint256 tokenId) internal virtual override {
        emit Approval(owner, approved, tokenId);
    }

    function emitApprovalForAll(address owner, address operator, bool approved) internal virtual override {
        emit ApprovalForAll(owner, operator, approved);
    }

    function balanceOf(address owner) public view virtual returns (uint256) {
        SupplyPosition storage sp = supplyPositionStorage();

        if (owner == address(0)) {
            revert ERC721AddressZeroIsNotAValidOwner();
        }
        return sp.balance[owner];
    }

    function ownerOf(uint256 tokenId) public view virtual returns (address) {
        return _ownerOf(tokenId);
    }

    function name() public view virtual returns (string memory) {
        SupplyPosition storage sp = supplyPositionStorage();

        return sp.name;
    }

    function symbol() public view virtual returns (string memory) {
        SupplyPosition storage sp = supplyPositionStorage();

        return sp.symbol;
    }

    function approve(address to, uint256 tokenId) public virtual {
        address owner = ownerOf(tokenId);
        if (to == owner) {
            revert ERC721ApprovalToCurrentOwner();
        }
        if (msg.sender != owner && !isApprovedForAll(owner, msg.sender)) {
            revert ERC721CallerIsNotOwnerNorApprovedForAll();
        }

        _approve(to, tokenId);
    }

    function getApproved(uint256 tokenId) public view virtual returns (address) {
        return _getApproved(tokenId);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        _setApprovalForAll(msg.sender, operator, approved);
    }

    function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
        return _isApprovedForAll(owner, operator);
    }

    function transferFrom(address from, address to, uint256 tokenId) public virtual {
        if (!_isApprovedOrOwner(msg.sender, tokenId)) {
            revert ERC721CallerIsNotOwnerNorApproved();
        }

        _transfer(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId) public virtual {
        safeTransferFrom(from, to, tokenId, "");
    }

    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
        if (!_isApprovedOrOwner(msg.sender, tokenId)) {
            revert ERC721CallerIsNotOwnerNorApproved();
        }
        _safeTransfer(from, to, tokenId, data);
    }
}
合同源代码
文件 10 的 36:ERC721Errors.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

error ERC721AddressZeroIsNotAValidOwner();
error ERC721InvalidTokenId();
error ERC721ApprovalToCurrentOwner();
error ERC721CallerIsNotOwnerNorApprovedForAll();
error ERC721CallerIsNotOwnerNorApproved();
error ERC721TransferToNonERC721ReceiverImplementer();
error ERC721MintToTheZeroAddress();
error ERC721TokenAlreadyMinted();
error ERC721TransferFromIncorrectOwner();
error ERC721TransferToTheZeroAddress();
error ERC721ApproveToCaller();
error ERC721CallerIsNotOwner();
合同源代码
文件 11 的 36:Erc20CheckedTransfer.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {ERC20TransferFailed} from "../DataStructure/Errors.sol";

/// @notice library to safely transfer ERC20 tokens, including not entirely compliant tokens like BNB and USDT
/// @dev avoids bugs due to tokens not following the erc20 standard by not returning a boolean
///     or by reverting on 0 amount transfers. Does not support fee on transfer tokens
library Erc20CheckedTransfer {
    using SafeERC20 for IERC20;

    /// @notice executes only if amount is greater than zero
    /// @param amount amount to check
    modifier skipZeroAmount(uint256 amount) {
        if (amount > 0) {
            _;
        }
    }

    /// @notice safely transfers
    /// @param currency ERC20 to transfer
    /// @param from sender
    /// @param to recipient
    /// @param amount amount to transfer
    function checkedTransferFrom(
        IERC20 currency,
        address from,
        address to,
        uint256 amount
    ) internal skipZeroAmount(amount) {
        currency.safeTransferFrom(from, to, amount);
    }

    /// @notice safely transfers
    /// @param currency ERC20 to transfer
    /// @param to recipient
    /// @param amount amount to transfer
    function checkedTransfer(IERC20 currency, address to, uint256 amount) internal skipZeroAmount(amount) {
        currency.safeTransfer(to, amount);
    }
}
合同源代码
文件 12 的 36:Errors.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {NFToken, Offer} from "./Objects.sol";

error BadCollateral(Offer offer, NFToken providedNft);
error ERC20TransferFailed(IERC20 token, address from, address to);
error OfferHasExpired(Offer offer, uint256 expirationDate);
error RequestedAmountIsUnderMinimum(Offer offer, uint256 requested, uint256 lowerBound);
error RequestedAmountTooHigh(uint256 requested, uint256 offered, Offer offer);
error LoanAlreadyRepaid(uint256 loanId);
error LoanNotRepaidOrLiquidatedYet(uint256 loanId);
error NotBorrowerOfTheLoan(uint256 loanId);
error BorrowerAlreadyClaimed(uint256 loanId);
error CallerIsNotOwner(address admin);
error InvalidTranche(uint256 nbOfTranches);
error CollateralIsNotLiquidableYet(uint256 endDate, uint256 loanId);
error UnsafeAmountLent(uint256 lent);
error MultipleOffersUsed();
error PriceOverMaximum(uint256 maxPrice, uint256 price);
error CurrencyNotSupported(IERC20 currency);
error ShareMatchedIsTooLow(Offer offer, uint256 requested);
合同源代码
文件 13 的 36:FuncSelectors.h.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IDiamondCut} from "diamond/contracts/interfaces/IDiamondCut.sol";
import {IDiamondLoupe} from "diamond/contracts/interfaces/IDiamondLoupe.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC165} from "diamond/contracts/interfaces/IERC165.sol";
import {OwnershipFacet} from "diamond/contracts/facets/OwnershipFacet.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

import {IAuctionFacet} from "../interface/IAuctionFacet.sol";
import {IBorrowFacet} from "../interface/IBorrowFacet.sol";
import {IClaimFacet} from "../interface/IClaimFacet.sol";
import {IProtocolFacet} from "../interface/IProtocolFacet.sol";
import {IRepayFacet} from "../interface/IRepayFacet.sol";
import {IAdminFacet} from "../interface/IAdminFacet.sol";
import {ISignature} from "../interface/ISignature.sol";

import {SupplyPositionFacet} from "../SupplyPositionFacet.sol";
import {DiamondERC721} from "../SupplyPositionLogic/DiamondERC721.sol";

/* solhint-disable func-visibility */

/// @notice This file is for function selectors getters of facets
/// @dev create a new function for each new facet and update them
///     according to their interface

function loupeFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](5);

    functionSelectors[0] = IDiamondLoupe.facets.selector;
    functionSelectors[1] = IDiamondLoupe.facetFunctionSelectors.selector;
    functionSelectors[2] = IDiamondLoupe.facetAddresses.selector;
    functionSelectors[3] = IDiamondLoupe.facetAddress.selector;
    functionSelectors[4] = IERC165.supportsInterface.selector;

    return functionSelectors;
}

function ownershipFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](2);

    functionSelectors[0] = OwnershipFacet.transferOwnership.selector;
    functionSelectors[1] = OwnershipFacet.owner.selector;

    return functionSelectors;
}

function cutFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](1);

    functionSelectors[0] = IDiamondCut.diamondCut.selector;

    return functionSelectors;
}

function borrowFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](3);

    functionSelectors[0] = IERC721Receiver.onERC721Received.selector;
    functionSelectors[1] = ISignature.offerDigest.selector;
    functionSelectors[2] = IBorrowFacet.borrow.selector;

    return functionSelectors;
}

function supplyPositionFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](13);

    functionSelectors[0] = IERC721.balanceOf.selector;
    functionSelectors[1] = IERC721.ownerOf.selector;
    functionSelectors[2] = DiamondERC721.name.selector;
    functionSelectors[3] = DiamondERC721.symbol.selector;
    functionSelectors[4] = IERC721.approve.selector;
    functionSelectors[5] = IERC721.getApproved.selector;
    functionSelectors[6] = IERC721.setApprovalForAll.selector;
    functionSelectors[7] = IERC721.isApprovedForAll.selector;
    functionSelectors[8] = IERC721.transferFrom.selector;
    functionSelectors[9] = getSelector("safeTransferFrom(address,address,uint256)");
    functionSelectors[10] = getSelector("safeTransferFrom(address,address,uint256,bytes)");
    functionSelectors[11] = SupplyPositionFacet.position.selector;
    functionSelectors[12] = SupplyPositionFacet.totalSupply.selector;

    return functionSelectors;
}

/// @notice protocol facet function selectors
function protoFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](4);

    functionSelectors[0] = IProtocolFacet.getRateOfTranche.selector;
    functionSelectors[1] = IProtocolFacet.getParameters.selector;
    functionSelectors[2] = IProtocolFacet.getLoan.selector;
    functionSelectors[3] = IProtocolFacet.getMinOfferCostAndBorrowableAmount.selector;

    return functionSelectors;
}

function repayFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](1);

    functionSelectors[0] = IRepayFacet.repay.selector;

    return functionSelectors;
}

function auctionFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](2);

    functionSelectors[0] = IAuctionFacet.buy.selector;
    functionSelectors[1] = IAuctionFacet.price.selector;

    return functionSelectors;
}

function claimFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](2);

    functionSelectors[0] = IClaimFacet.claim.selector;
    functionSelectors[1] = IClaimFacet.claimAsBorrower.selector;

    return functionSelectors;
}

function adminFS() pure returns (bytes4[] memory) {
    bytes4[] memory functionSelectors = new bytes4[](5);

    functionSelectors[0] = IAdminFacet.setAuctionDuration.selector;
    functionSelectors[1] = IAdminFacet.setAuctionPriceFactor.selector;
    functionSelectors[2] = IAdminFacet.createTranche.selector;
    functionSelectors[3] = IAdminFacet.setMinOfferCost.selector;
    functionSelectors[4] = IAdminFacet.setBorrowAmountPerOfferLowerBound.selector;

    return functionSelectors;
}

function getSelector(string memory _func) pure returns (bytes4) {
    return bytes4(keccak256(bytes(_func)));
}
合同源代码
文件 14 的 36:Global.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {Protocol, SupplyPosition} from "./Storage.sol";
import {Ray} from "./Objects.sol";

/* rationale of the naming of the hash is to use kairos loan's ENS as domain, the subject of the storage struct as
subdomain and the version to anticipate upgrade. Order is revered compared to urls as it's the usage in code such as in
java imports */
bytes32 constant PROTOCOL_SP = keccak256("eth.kairosloan.protocol.v1.0");
bytes32 constant SUPPLY_SP = keccak256("eth.kairosloan.supply-position.v1.0");

/* Ray is chosed as the only fixed-point decimals approach as it allow extreme and versatile precision accross erc20s
and timeframes */
uint256 constant RAY = 1e27;
Ray constant ONE = Ray.wrap(RAY);
Ray constant ZERO = Ray.wrap(0);

/* solhint-disable func-visibility */

/// @dev getters of storage regions of the contract for specified usage

/* we access storage only through functions in facets following the diamond storage pattern */

function protocolStorage() pure returns (Protocol storage protocol) {
    bytes32 position = PROTOCOL_SP;
    /* solhint-disable-next-line no-inline-assembly */
    assembly {
        protocol.slot := position
    }
}

function supplyPositionStorage() pure returns (SupplyPosition storage sp) {
    bytes32 position = SUPPLY_SP;
    /* solhint-disable-next-line no-inline-assembly */
    assembly {
        sp.slot := position
    }
}
合同源代码
文件 15 的 36:IAdminFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {Ray} from "../DataStructure/Objects.sol";

interface IAdminFacet {
    /// @notice duration of future auctions has been updated
    /// @param newAuctionDuration duration of liquidation for new loans
    event NewAuctionDuration(uint256 indexed newAuctionDuration);

    /// @notice initial price factor of future auctions has been updated
    /// @param newAuctionPriceFactor factor of loan to value setting initial price of auctions
    event NewAuctionPriceFactor(Ray indexed newAuctionPriceFactor);

    /// @notice a new interest rate tranche has been created
    /// @param tranche the interest rate of the new tranche, in multiplier per second
    /// @param newTrancheId identifier of the new tranche
    event NewTranche(Ray indexed tranche, uint256 indexed newTrancheId);

    /// @notice the minimum cost to repay per used loan offer
    ///     when borrowing a certain currency has been updated
    /// @param currency the erc20 on which a new minimum borrow cost is taking effect
    /// @param newMinOfferCost the new minimum amount that will need to be repaid per loan offer used
    event NewMininimumOfferCost(IERC20 indexed currency, uint256 indexed newMinOfferCost);

    /// @notice the borrow amount lower bound per offer has been updated
    /// @param currency the erc20 on which a new borrow amount lower bound is taking effect
    /// @param newLowerBound the new lower bound
    event NewBorrowAmountPerOfferLowerBound(IERC20 indexed currency, uint256 indexed newLowerBound);

    function setAuctionDuration(uint256 newAuctionDuration) external;

    function setAuctionPriceFactor(Ray newAuctionPriceFactor) external;

    function createTranche(Ray newTranche) external returns (uint256 newTrancheId);

    function setMinOfferCost(IERC20 currency, uint256 newMinOfferCost) external;

    function setBorrowAmountPerOfferLowerBound(IERC20 currency, uint256 newLowerBound) external;
}
合同源代码
文件 16 的 36:IAuctionFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {BuyArg} from "../DataStructure/Objects.sol";

interface IAuctionFacet {
    /// @notice a NFT collateral has been sold as part of a liquidation
    /// @param loanId identifier of the loan previously backed by the sold collateral
    /// @param args arguments NFT sold
    event Buy(uint256 indexed loanId, bytes args);

    function buy(BuyArg[] memory args) external;

    function price(uint256 loanId) external view returns (uint256);
}
合同源代码
文件 17 的 36:IBorrowCheckers.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

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

/* solhint-disable-next-line no-empty-blocks */
interface IBorrowCheckers is ISignature {

}
合同源代码
文件 18 的 36:IBorrowFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

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

import {BorrowArg} from "../DataStructure/Objects.sol";

interface IBorrowFacet is IBorrowHandlers, IERC721Receiver {
    function borrow(BorrowArg[] calldata args) external;
}
合同源代码
文件 19 的 36:IBorrowHandlers.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

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

/* solhint-disable-next-line no-empty-blocks */
interface IBorrowHandlers is IBorrowCheckers {
    /// @notice one loan has been initiated
    /// @param loanId id of the loan
    /// @param loan the loan created
    event Borrow(uint256 indexed loanId, bytes loan);
}
合同源代码
文件 20 的 36:IClaimFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

interface IClaimEmitter {
    /// @notice some liquidity has been claimed as principal plus interests or share of liquidation
    /// @param claimant who received the liquidity
    /// @param claimed amount sent
    /// @param loanId loan identifier where the claim rights come from
    event Claim(address indexed claimant, uint256 indexed claimed, uint256 indexed loanId);
}

interface IClaimFacet is IClaimEmitter {
    function claim(uint256[] calldata positionIds) external returns (uint256 sent);

    function claimAsBorrower(uint256[] calldata loanIds) external returns (uint256 sent);
}
合同源代码
文件 21 的 36:IOwnershipFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

interface IOwnershipFacet {
    function transferOwnership(address _newOwner) external;

    function owner() external view returns (address owner_);
}
合同源代码
文件 22 的 36:IProtocolFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {Ray} from "../DataStructure/Objects.sol";
import {Loan} from "../DataStructure/Storage.sol";

interface IProtocolFacet {
    function getRateOfTranche(uint256 id) external view returns (Ray rate);

    function getParameters()
        external
        view
        returns (Ray auctionPriceFactor, uint256 auctionDuration, uint256 nbOfLoans, uint256 nbOfTranches);

    function getLoan(uint256 id) external view returns (Loan memory);

    function getMinOfferCostAndBorrowableAmount(
        IERC20 currency
    ) external view returns (uint256 minOfferCost, uint256 offerBorrowAmountLowerBound);
}
合同源代码
文件 23 的 36:IRepayFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

interface IRepayFacet {
    /// @notice a loan has been repaid with interests by its borrower
    /// @param loanId loan identifier
    event Repay(uint256 indexed loanId);

    function repay(uint256[] memory loanIds) external;
}
合同源代码
文件 24 的 36:ISignature.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {Offer} from "../DataStructure/Objects.sol";

interface ISignature {
    function offerDigest(Offer memory offer) external view returns (bytes32);
}
合同源代码
文件 25 的 36:Initializer.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

// Derived from Nick Mudge's DiamondInit from the reference diamond implementation

import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {LibDiamond} from "diamond/contracts/libraries/LibDiamond.sol";
import {IDiamondLoupe} from "diamond/contracts/interfaces/IDiamondLoupe.sol";
import {IDiamondCut} from "diamond/contracts/interfaces/IDiamondCut.sol";
import {IERC173} from "diamond/contracts/interfaces/IERC173.sol";
import {IERC165} from "diamond/contracts/interfaces/IERC165.sol";

import {ONE, protocolStorage, supplyPositionStorage} from "./DataStructure/Global.sol";
import {Ray} from "./DataStructure/Objects.sol";
import {Protocol, SupplyPosition} from "./DataStructure/Storage.sol";
import {RayMath} from "./utils/RayMath.sol";

/// @notice initilizes the kairos protocol
contract Initializer {
    using RayMath for Ray;

    /// @notice initilizes the kairos protocol
    /// @dev specify this method in diamond constructor
    function init() external {
        // adding ERC165 data
        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
        ds.supportedInterfaces[type(IERC165).interfaceId] = true;
        ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true;
        ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true;
        ds.supportedInterfaces[type(IERC173).interfaceId] = true;

        // initializing protocol
        Protocol storage proto = protocolStorage();

        proto.tranche[0] = ONE.div(10).mul(4).div(365 days); // 40% APR
        proto.nbOfTranches = 1;
        proto.auction.priceFactor = ONE.mul(3);
        proto.auction.duration = 3 days;

        // initializing supply position nft collection
        SupplyPosition storage sp = supplyPositionStorage();
        sp.name = "Kairos Supply Position";
        sp.symbol = "KSP";
        ds.supportedInterfaces[type(IERC721).interfaceId] = true;
    }
}
合同源代码
文件 26 的 36:KairosEagle.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

//                            .a@@@@@#########@@@@a.
//                        .a@@######@@@mm@@mm######@@@a.
//                   .a####@@@@@@@@@@@@@@@@@@@mm@@##@@v;%%,.
//                .a###v@@@@@@@@vvvvvvvvvvvvvv@@@@#@v;%%%vv%%,
//             .a##vv@@@@@@@@vv%%%%;S,  .S;%%vv@@#v;%%'/%vvvv%;
//           .a##@v@@@@@vv%%vvvvvv%%;SssS;%%vvvv@v;%%./%vvvvvv%;
//         ,a##vv@@@vv%%%@@@@@@@@@@@@mmmmmmmmmvv;%%%%vvvvvvvvv%;
//         .a##@@@@@@@@@@@@@@@@@@@@@@@mmmmmvv;%%%%%vvvvvvvvvvv%;
//        ###vv@@@v##@v@@@@@@@@@@mmv;%;%;%;%;%;%;%;%;%;%;%,%vv%'
//       a#vv@@@@v##v@@@@@@@@###@@@@@%v%v%v%v%v%v%v%      ;%%;'
//      ',a@@@@@@@v@@@@@@@@v###v@@@nvnvnvnvnvnvnvnv'     .%;'
//      a###@@@@@@@###v@@@v##v@@@mnmnmnmnmnmnmnmnmn.     ;'
//     ,###vv@@@@v##v@@@@@@v@@@@v##v@@@@@v###v@@@##@.
//     ###vv@@@@@@v@@###v@@@@@@@@v@@@@@@v##v@@@v###v@@.
//    a@vv@@@@@@@@@v##v@@@@@@@@@@@@@@;@@@v@@@@v##v@@@@@@a
//   ',@@@@@@;@@@@@@v@@@@@@@@@@@@@@@;%@@@@@@@@@v@@@@;@@@@@a
//  .@@@@@@;%@@;@@@@@@@;;@@@@@;@@@@;%%;@@@@@;@@@@;@@@;@@@@@@.
// ,a@@@;vv;@%;@@@@@;%%v%;@@@;@@@;%vv%%;@@@;%;@@;%@@@;%;@@;%@@a
//   .@@@@@@;%@@;@@@@@@@;;@@@@@;@@@@;%%;@@@@@;@@@@;@@@;@@@@@@.
// ,a@@@;vv;@%;@@@@@;%%v%;@@@;@@@;%vv%%;@@@;%;@@;%@@@;%;@@;%@@a
//  a@;vv;%%%;@@;%%;vvv;%%@@;%;@;%vvv;%;@@;%%%;@;%;@;%%%@@;%%;.`
// ;@;%;vvv;%;@;%%;vv;%%%%v;%%%;%vv;%%v;@@;%vvv;%;%;%;%%%;%%%%;.%,
// %%%;vv;%;vv;%%%v;%%%%;vvv;%%%v;%%%;vvv;%;vv;%%%%%;vv;%%%;vvv;.%%%,
// ;vvv;%%;vv;%%;vv;%%%;vv;%%%;vvv;%;vv;%;vv;%;%%%;vv;%%%%;vv;%%.%v;v%
// vv;%;vvv;%;vv;;%%%%%%;%%%%;vv;%%%%;%%%%;%%;%%;%%;vv;%%%%;%%%;v.%vv;
// ;%%%%;%%%%%;%%%%%;%%%%;%%%%;%%%;%%%;%%%%%%%;%%%%%;%%%;%%%%;%%;.%%;%

// &&&&@7   :B@@@@P.     5@@@@@@B       Y@&&@G  ?B&@@@@@@@@@@&#Y.       :!YGB#BG5?~       ^5#&@@@@@@@&7
// @@@@@7  ^#@@@@5.     !@@@@@@@@?      Y@@@@B  ^^#@@@@@##&@@@@@P     :Y&@@@@@@@@@@BJ.   ^&@@@@@####B!
// @@@@@7 ~&@@@@?      .#@@@&&@@@&:     Y@@@@B    #@@@@5 ..B@@@@#     5@GPGB@@@@@@@@@#~  ?@@@@@J.
// @@@@@7~&@@@&~       Y@@@@J?@@@@5     Y@@@@B   .#@@@@5   P@@@@#     .!^!YB@@@@@@@@@@#: :B@@@@&BY!:
// @@@@@7Y@@@@#:      ~@@@@#. #@@@@~    Y@@@@B   .#@@@@5~YP&@@@#7  :~JPB@@@@@@@@@@@@@@@~  .?G&@@@@@&BJ:
// @@@@@7 5@@@@#^     G@@@@G77G@@@@#7!. Y@@@@B   .#@@@@5.B@@@@G.  !&@@@@@@@@@@@@@@@@@@#:     .~JG@@@@@B
// @@@@@7  5@@@@&~   ?@@@@@@@@@@@@@@@?  Y@@@@B   .#@@@@5 .B@@@@Y   ^Y#@@@@@@@@@@@@@@@#~   .:....:G@@@@&
// @@@@@7   Y@@@@&! :&@@@@5!!!~Y@@@@&:  Y@@@@B   .#@@@@5  :B@@@@Y    ~#@@@@@@@@@@@@BJ.   .#&&&&&&@@@@@5
// ####&7    J&&&&#^Y&&&&B.     G&##&J  J&##&G   .G###&Y   ^B&&&&?    .~7JJYJPBG5?~      .B&&&&&&&#BP!

import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

import {IOwnershipFacet} from "../interface/IOwnershipFacet.sol";

import {DiamondERC721} from "../SupplyPositionLogic/DiamondERC721.sol";
import {SupplyPosition} from "../DataStructure/Storage.sol";
import {supplyPositionStorage} from "../DataStructure/Global.sol";
import {CallerIsNotOwner} from "../DataStructure/Errors.sol";

struct Raffle {
    bytes32 whitelistMerkleRoot;
    uint256 mintCap;
    uint256 mintCapPerAddress;
    uint256 wethUnitPrice;
    uint256 amountMinted;
    mapping(address => uint256) amountMintedBy;
}

struct EagleStorage {
    string baseMetadataUri;
    mapping(uint256 => Raffle) raffle;
}

bytes32 constant EAGLE_STORAGE_POSITION = keccak256("eth.kairosloan.eagle.v1.0");

/**
 * @notice eagleStorage
 */
function eagleStorage() pure returns (EagleStorage storage eagle) {
    bytes32 position = EAGLE_STORAGE_POSITION;
    /* solhint-disable-next-line no-inline-assembly */
    assembly {
        eagle.slot := position
    }
}

error InvalidMerkleProof();
error HardCapExceeded();
error RaffleMintCapExceeded();
error AccountMintCapExceeded();

contract KairosEagleFacet is DiamondERC721 {
    using MerkleProof for bytes32[];
    using Strings for uint256;

    IERC20 internal immutable wEth;
    uint256 internal constant HARD_CAP = 555;

    event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);

    modifier onlyOwner() {
        // the admin/owner is the same account that can upgrade the protocol.
        address admin = IOwnershipFacet(address(this)).owner();
        if (msg.sender != admin) {
            revert CallerIsNotOwner(admin);
        }
        _;
    }

    constructor(IERC20 _weth) {
        wEth = _weth;
    }

    /**
     * @notice buy
     */
    function buy(bytes32[] calldata merkleProof, uint256 raffleId) external returns (uint256) {
        Raffle storage raffle = eagleStorage().raffle[raffleId];
        bytes32 merkleRoot = raffle.whitelistMerkleRoot;
        uint256 amountMintedByCaller = ++raffle.amountMintedBy[msg.sender];
        uint256 amountMintedInRaffle = ++raffle.amountMinted;

        if (amountMintedByCaller > raffle.mintCapPerAddress) {
            revert AccountMintCapExceeded();
        }
        if (amountMintedInRaffle > raffle.mintCap) {
            revert RaffleMintCapExceeded();
        }
        if (!merkleProof.verifyCalldata(merkleRoot, keccak256(abi.encode(msg.sender)))) {
            revert InvalidMerkleProof();
        }

        wEth.transferFrom(msg.sender, address(this), raffle.wethUnitPrice);

        return mint();
    }

    /**
     * @notice setRaffle
     */
    function setRaffle(
        uint256 raffleId,
        bytes32 whitelistMerkleRoot,
        uint256 mintCapPerAddress,
        uint256 mintCap,
        uint256 wethUnitPrice
    ) external onlyOwner {
        Raffle storage raffle = eagleStorage().raffle[raffleId];
        raffle.whitelistMerkleRoot = whitelistMerkleRoot;
        raffle.mintCapPerAddress = mintCapPerAddress;
        raffle.mintCap = mintCap;
        raffle.wethUnitPrice = wethUnitPrice;
    }

    /**
     * @notice withdrawFunds
     */
    function withdrawFunds() external onlyOwner {
        wEth.transfer(msg.sender, wEth.balanceOf(address(this)));
    }

    /**
     * @notice setBaseMetadataUri
     */
    function setBaseMetadataUri(string calldata baseMetadataUri) external onlyOwner {
        eagleStorage().baseMetadataUri = baseMetadataUri;
        emit BatchMetadataUpdate(1, type(uint256).max); // the max uint signals refresh of the whole collection
    }

    /**
     * @notice tokenURI
     */
    function tokenURI(uint256 tokenId) external view returns (string memory) {
        return string(abi.encodePacked(eagleStorage().baseMetadataUri, tokenId.toString()));
    }

    /**
     * @notice totalSupply
     */
    function totalSupply() external view returns (uint256) {
        return supplyPositionStorage().totalSupply;
    }

    /**
     * @notice getHardCap
     */
    function getHardCap() external pure returns (uint256) {
        return HARD_CAP;
    }

    /**
     * @notice mint
     */
    function mint() internal returns (uint256 tokenId) {
        tokenId = ++supplyPositionStorage().totalSupply;
        if (tokenId >= HARD_CAP) {
            revert HardCapExceeded();
        }
        _safeMint(msg.sender, tokenId);
    }
}
合同源代码
文件 27 的 36:NFTUtils.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

import {supplyPositionStorage} from "../DataStructure/Global.sol";
import {SupplyPosition} from "../DataStructure/Storage.sol";
import {ERC721ApproveToCaller, ERC721InvalidTokenId, ERC721TokenAlreadyMinted, ERC721MintToTheZeroAddress, ERC721TransferFromIncorrectOwner, ERC721TransferToNonERC721ReceiverImplementer, ERC721TransferToTheZeroAddress} from "../DataStructure/ERC721Errors.sol";

/// @notice internal logic for DiamondERC721 adapted fo usage with diamond storage
abstract contract NFTUtils {
    using Address for address;

    function emitTransfer(address from, address to, uint256 tokenId) internal virtual;

    function emitApproval(address owner, address approved, uint256 tokenId) internal virtual;

    function emitApprovalForAll(address owner, address operator, bool approved) internal virtual;

    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert ERC721TransferToNonERC721ReceiverImplementer();
                } else {
                    /* solhint-disable-next-line no-inline-assembly */
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal {
        _transfer(from, to, tokenId);
        if (!_checkOnERC721Received(from, to, tokenId, data)) {
            revert ERC721TransferToNonERC721ReceiverImplementer();
        }
    }

    function _safeMint(address to, uint256 tokenId) internal {
        _safeMint(to, tokenId, "");
    }

    function _safeMint(address to, uint256 tokenId, bytes memory data) internal {
        _mint(to, tokenId);
        if (!_checkOnERC721Received(address(0), to, tokenId, data)) {
            revert ERC721TransferToNonERC721ReceiverImplementer();
        }
    }

    function _mint(address to, uint256 tokenId) internal {
        SupplyPosition storage sp = supplyPositionStorage();

        if (to == address(0)) {
            revert ERC721MintToTheZeroAddress();
        }
        if (_exists(tokenId)) {
            revert ERC721TokenAlreadyMinted();
        }

        sp.balance[to] += 1;
        sp.owner[tokenId] = to;

        emitTransfer(address(0), to, tokenId);
    }

    function _burn(uint256 tokenId) internal {
        SupplyPosition storage sp = supplyPositionStorage();

        address owner = _ownerOf(tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        sp.balance[owner] -= 1;
        delete sp.owner[tokenId];

        emitTransfer(owner, address(0), tokenId);
    }

    function _transfer(address from, address to, uint256 tokenId) internal {
        SupplyPosition storage sp = supplyPositionStorage();

        if (_ownerOf(tokenId) != from) {
            revert ERC721TransferFromIncorrectOwner();
        }
        if (to == address(0)) {
            revert ERC721TransferToTheZeroAddress();
        }

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        sp.balance[from] -= 1;
        sp.balance[to] += 1;
        sp.owner[tokenId] = to;

        emitTransfer(from, to, tokenId);
    }

    function _approve(address to, uint256 tokenId) internal {
        SupplyPosition storage sp = supplyPositionStorage();

        sp.tokenApproval[tokenId] = to;
        emitApproval(_ownerOf(tokenId), to, tokenId);
    }

    function _setApprovalForAll(address owner, address operator, bool approved) internal {
        SupplyPosition storage sp = supplyPositionStorage();

        if (owner == operator) {
            revert ERC721ApproveToCaller();
        }
        sp.operatorApproval[owner][operator] = approved;
        emitApprovalForAll(owner, operator, approved);
    }

    function _exists(uint256 tokenId) internal view returns (bool) {
        SupplyPosition storage sp = supplyPositionStorage();

        return sp.owner[tokenId] != address(0);
    }

    function _ownerOf(uint256 tokenId) internal view returns (address) {
        SupplyPosition storage sp = supplyPositionStorage();

        address owner = sp.owner[tokenId];
        if (owner == address(0)) {
            revert ERC721InvalidTokenId();
        }
        return owner;
    }

    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
        address owner = _ownerOf(tokenId);
        return (spender == owner || _isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
    }

    function _getApproved(uint256 tokenId) internal view returns (address) {
        if (!_exists(tokenId)) {
            revert ERC721InvalidTokenId();
        }

        return supplyPositionStorage().tokenApproval[tokenId];
    }

    function _isApprovedForAll(address owner, address operator) internal view returns (bool) {
        return supplyPositionStorage().operatorApproval[owner][operator];
    }
}
合同源代码
文件 28 的 36:NFTokenUtils.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {NFToken} from "../DataStructure/Objects.sol";

/// @notice Manipulates NFTs
library NFTokenUtils {
    /// @notice `a` is the the same NFT as `b`
    /// @return result
    function eq(NFToken memory a, NFToken memory b) internal pure returns (bool) {
        return (a.id == b.id && a.implem == b.implem);
    }
}
合同源代码
文件 29 的 36:Objects.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/// @notice file for type definitions not used in storage

/// @notice 27-decimals fixed point unsigned number
type Ray is uint256;

/// @notice Arguments to buy the collateral of one loan
/// @param loanId loan identifier
/// @param to address that will receive the collateral
/// @param maxPrice maximum price to pay for the collateral
struct BuyArg {
    uint256 loanId;
    address to;
    uint256 maxPrice;
}

/// @notice Arguments to borrow from one collateral
/// @param nft asset to use as collateral
/// @param args arguments for the borrow parameters of the offers to use with the collateral
struct BorrowArg {
    NFToken nft;
    OfferArg[] args;
}

/// @notice Arguments for the borrow parameters of an offer
/// @dev '-' means n^th
/// @param signature - of the offer
/// @param amount - to borrow from this offer
/// @param offer intended for usage in the loan
struct OfferArg {
    bytes signature;
    uint256 amount;
    Offer offer;
}

/// @notice Data on collateral state during the matching process of a NFT
///     with multiple offers
/// @param matched proportion from 0 to 1 of the collateral value matched by offers
/// @param assetLent - ERC20 that the protocol will send as loan
/// @param tranche identifier of the interest rate tranche that will be used for the loan
/// @param minOfferDuration minimal duration among offers used
/// @param minOfferLoanToValue
/// @param maxOfferLoanToValue
/// @param from original owner of the nft (borrower in most cases)
/// @param nft the collateral asset
/// @param loanId loan identifier
struct CollateralState {
    Ray matched;
    IERC20 assetLent;
    uint256 tranche;
    uint256 minOfferDuration;
    uint256 minOfferLoanToValue;
    uint256 maxOfferLoanToValue;
    address from;
    NFToken nft;
    uint256 loanId;
}

/// @notice Loan offer
/// @param assetToLend address of the ERC-20 to lend
/// @param loanToValue amount to lend per collateral
/// @param duration in seconds, time before mandatory repayment after loan start
/// @param expirationDate date after which the offer can't be used
/// @param tranche identifier of the interest rate tranche
/// @param collateral the NFT that can be used as collateral with this offer
struct Offer {
    IERC20 assetToLend;
    uint256 loanToValue;
    uint256 duration;
    uint256 expirationDate;
    uint256 tranche;
    NFToken collateral;
}

/// @title Non Fungible Token
/// @notice describes an ERC721 compliant token, can be used as single spec
///     I.e Collateral type accepting one specific NFT
/// @dev found in storgae
/// @param implem address of the NFT contract
/// @param id token identifier
struct NFToken {
    IERC721 implem;
    uint256 id;
}
合同源代码
文件 30 的 36:ProtocolFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IProtocolFacet} from "./interface/IProtocolFacet.sol";

import {Loan, Protocol} from "./DataStructure/Storage.sol";
import {protocolStorage} from "./DataStructure/Global.sol";
import {Ray} from "./DataStructure/Objects.sol";

/// @notice external loupe functions exposing protocol storage
contract ProtocolFacet is IProtocolFacet {
    /// @notice gets the rate of tranche `id`
    /// @param id rate identifier
    /// @return rate the rate of the tranche, as a Ray, multiplier per second of the amount to repay (non compounding)
    ///         I.e lent * time since loan start * tranche = interests to repay
    function getRateOfTranche(uint256 id) external view returns (Ray rate) {
        return protocolStorage().tranche[id];
    }

    /// @notice gets current values of parameters impacting loans behavior and total number of loans (active and ended)
    /// @return auctionPriceFactor factor multiplied with the loan to value of a loan to get initial price
    ///         of a collateral on sale
    /// @return auctionDuration number of seconds after the auction start when the price hits 0
    /// @return nbOfLoans total number of loans ever issued (active and ended)
    /// @return nbOfTranches total number of interest rates tranches ever created (active and inactive)
    function getParameters()
        external
        view
        returns (Ray auctionPriceFactor, uint256 auctionDuration, uint256 nbOfLoans, uint256 nbOfTranches)
    {
        Protocol storage proto = protocolStorage();
        auctionPriceFactor = proto.auction.priceFactor;
        auctionDuration = proto.auction.duration;
        nbOfLoans = proto.nbOfLoans;
        nbOfTranches = proto.nbOfTranches;
    }

    /// @notice get loan metadata
    /// @param id loan identifier
    /// @return loan the corresponding loan
    function getLoan(uint256 id) external view returns (Loan memory) {
        return protocolStorage().loan[id];
    }

    /// @notice get the minimal amount to repay per offer used in loan and minimum amount to borrow per offer.
    ///     Gives currently active settings, may not concern loans already issued.
    /// @param currency the erc20 asset lent on which the parameters apply
    /// @return minOfferCost the minimal amount to repay per offer used in loan
    /// @return offerBorrowAmountLowerBound  minimum amount to borrow per offer used in loan
    function getMinOfferCostAndBorrowableAmount(
        IERC20 currency
    ) external view returns (uint256 minOfferCost, uint256 offerBorrowAmountLowerBound) {
        Protocol storage proto = protocolStorage();
        minOfferCost = proto.minOfferCost[currency];
        offerBorrowAmountLowerBound = proto.offerBorrowAmountLowerBound[currency];
    }
}
合同源代码
文件 31 的 36:RayMath.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {RAY} from "../DataStructure/Global.sol";
import {Ray} from "../DataStructure/Objects.sol";

/// @notice Manipulates fixed-point unsigned decimals numbers
/// @dev all uints are considered integers (no wad)
library RayMath {
    // ~~~ calculus ~~~ //

    /// @notice `a` plus `b`
    /// @return result
    function add(Ray a, Ray b) internal pure returns (Ray) {
        return Ray.wrap(Ray.unwrap(a) + Ray.unwrap(b));
    }

    /// @notice `a` minus `b`
    /// @return result
    function sub(Ray a, Ray b) internal pure returns (Ray) {
        return Ray.wrap(Ray.unwrap(a) - Ray.unwrap(b));
    }

    /// @notice `a` times `b`
    /// @return result
    function mul(Ray a, Ray b) internal pure returns (Ray) {
        return Ray.wrap((Ray.unwrap(a) * Ray.unwrap(b)) / RAY);
    }

    /// @notice `a` times `b`
    /// @return result
    function mul(Ray a, uint256 b) internal pure returns (Ray) {
        return Ray.wrap(Ray.unwrap(a) * b);
    }

    /// @notice `a` times `b`
    /// @return result
    function mul(uint256 a, Ray b) internal pure returns (uint256) {
        return (a * Ray.unwrap(b)) / RAY;
    }

    /// @notice `a` divided by `b`
    /// @return result
    function div(Ray a, Ray b) internal pure returns (Ray) {
        return Ray.wrap((Ray.unwrap(a) * RAY) / Ray.unwrap(b));
    }

    /// @notice `a` divided by `b`
    /// @return result
    function div(Ray a, uint256 b) internal pure returns (Ray) {
        return Ray.wrap(Ray.unwrap(a) / b);
    }

    /// @notice `a` divided by `b`
    /// @return result
    function div(uint256 a, Ray b) internal pure returns (uint256) {
        return (a * RAY) / Ray.unwrap(b);
    }

    /// @notice `a` divided by `b`
    /// @return result
    function div(uint256 a, uint256 b) internal pure returns (Ray) {
        return Ray.wrap((a * RAY) / b);
    }

    // ~~~ comparisons ~~~ //

    /// @notice is `a` less than `b`
    /// @return result
    function lt(Ray a, Ray b) internal pure returns (bool) {
        return Ray.unwrap(a) < Ray.unwrap(b);
    }

    /// @notice is `a` greater than `b`
    /// @return result
    function gt(Ray a, Ray b) internal pure returns (bool) {
        return Ray.unwrap(a) > Ray.unwrap(b);
    }

    /// @notice is `a` greater or equal to `b`
    /// @return result
    function gte(Ray a, Ray b) internal pure returns (bool) {
        return Ray.unwrap(a) >= Ray.unwrap(b);
    }

    /// @notice is `a` equal to `b`
    /// @return result
    function eq(Ray a, Ray b) internal pure returns (bool) {
        return Ray.unwrap(a) == Ray.unwrap(b);
    }

    // ~~~ uint256 method ~~~ //

    /// @notice highest value among `a` and `b`
    /// @return maximum
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }
}
合同源代码
文件 32 的 36:RepayFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IRepayFacet} from "./interface/IRepayFacet.sol";

import {Loan, Protocol} from "./DataStructure/Storage.sol";
import {LoanAlreadyRepaid} from "./DataStructure/Errors.sol";
import {protocolStorage} from "./DataStructure/Global.sol";
import {Ray} from "./DataStructure/Objects.sol";
import {RayMath} from "./utils/RayMath.sol";
import {Erc20CheckedTransfer} from "./utils/Erc20CheckedTransfer.sol";

/// @notice handles repayment with interests of loans
contract RepayFacet is IRepayFacet {
    using RayMath for Ray;
    using RayMath for uint256;
    using Erc20CheckedTransfer for IERC20;

    /// @notice repay one or multiple loans, gives collaterals back
    /// @dev repay on behalf is activated, the collateral goes to the original borrower
    /// @param loanIds identifiers of loans to repay
    function repay(uint256[] memory loanIds) external {
        Protocol storage proto = protocolStorage();
        Loan storage loan;
        uint256 lent;
        uint256 interests;
        uint256 toRepay;

        for (uint256 i = 0; i < loanIds.length; i++) {
            loan = proto.loan[loanIds[i]];
            // loan.payment.paid may be at 0 and considered repaid in case of an auction sale executed at price 0
            if (loan.payment.paid > 0 || loan.payment.borrowerClaimed || loan.payment.liquidated) {
                revert LoanAlreadyRepaid(loanIds[i]);
            }
            lent = loan.lent;
            /* if the linear interests are very low due to a short time elapsed, the minimal interests amount to repay
            is applied as an anti ddos mechanism */
            interests = RayMath.max(
                /* during the interests calculus, we can consider that (block.timestamp - loan.startDate)
                won't exceed 1e10 (>100 years) and interest per second (unwrapped value) won't exceed
                1e27 (corresponding to an amount to repay doubling after 1 second), we can deduce that
                (loan.interestPerSecond.mul(block.timestamp - loan.startDate)) is capped by 1e10 * 1e27 = 1e37
                we want to avoid the interests calculus to overflow so the result must not exceed 1e77
                as (1e77 < type(uint256).max). So we can allow `lent` to go as high as 1e40, but not above.
                This explains why borrowing throws on loan.lent > 1e40, as this realisticly avoids
                repaying being impossible due to an overflow. */
                /* the interest per second is a share of what has been lent to add to the interests each second. The
                next line accrues linearly */
                lent.mul(loan.interestPerSecond.mul(block.timestamp - loan.startDate)),
                loan.payment.minInterestsToRepay
            );
            toRepay = lent + interests;
            loan.payment.paid = toRepay;
            loan.payment.borrowerClaimed = true;
            loan.assetLent.checkedTransferFrom(msg.sender, address(this), toRepay);
            loan.collateral.implem.safeTransferFrom(address(this), loan.borrower, loan.collateral.id);
            emit Repay(loanIds[i]);
        }
    }
}
合同源代码
文件 33 的 36:SafeMint.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import {NFTUtils} from "./NFTUtils.sol";
import {Provision, SupplyPosition} from "../DataStructure/Storage.sol";
import {supplyPositionStorage} from "../DataStructure/Global.sol";

/// @notice safeMint internal method added to base ERC721 implementation for supply position minting
/// @dev inherit this to make an ERC721-compliant facet with added feature internal safeMint
contract SafeMint is NFTUtils {
    /// @notice mints a new supply position to `to`
    /// @param to receiver of the position
    /// @param provision metadata of the supply position
    /// @return tokenId identifier of the supply position
    function safeMint(address to, Provision memory provision) internal returns (uint256 tokenId) {
        SupplyPosition storage sp = supplyPositionStorage();

        tokenId = ++sp.totalSupply;
        sp.provision[tokenId] = provision;
        _safeMint(to, tokenId);
    }

    /* solhint-disable no-empty-blocks */
    function emitTransfer(address from, address to, uint256 tokenId) internal virtual override {}

    function emitApproval(address owner, address approved, uint256 tokenId) internal virtual override {}

    function emitApprovalForAll(address owner, address operator, bool approved) internal virtual override {}
}
合同源代码
文件 34 的 36:Signature.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

import {ISignature} from "./interface/ISignature.sol";

import {NFToken} from "./DataStructure/Objects.sol";
import {Offer} from "./DataStructure/Objects.sol";

/// @notice handles signature verification
abstract contract Signature is ISignature, EIP712 {
    bytes32 internal constant OFFER_TYPEHASH =
        keccak256(
            "Offer(address assetToLend,uint256 loanToValue,uint256 duration,"
            "uint256 expirationDate,uint256 tranche,NFToken collateral)"
            "NFToken(address implem,uint256 id)"
        ); // strings based on ethers js output
    bytes32 internal constant NFTOKEN_TYPEHASH = keccak256("NFToken(address implem,uint256 id)");

    /* solhint-disable-next-line no-empty-blocks */
    constructor() EIP712("Kairos Loan protocol", "0.1") {}

    /// @notice computes EIP-712 compliant digest of a loan offer
    /// @param offer the loan offer signed/to sign by a supplier
    /// @return digest the digest, ecdsa sign as is to produce a valid loan offer signature
    function offerDigest(Offer memory offer) public view returns (bytes32) {
        return _hashTypedDataV4(typeHashOffer(offer));
    }

    /// @notice computes EIP-712 hashStruct of an nfToken
    /// @param nft - to get the hash from
    /// @return the hashStruct
    function typeHashNFToken(NFToken memory nft) internal pure returns (bytes32) {
        return keccak256(abi.encode(NFTOKEN_TYPEHASH, nft));
    }

    /// @notice computes EIP-712 hashStruct of an offer
    /// @param offer the loan offer to hash
    /// @return the hashStruct
    function typeHashOffer(Offer memory offer) internal pure returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    OFFER_TYPEHASH,
                    offer.assetToLend,
                    offer.loanToValue,
                    offer.duration,
                    offer.expirationDate,
                    offer.tranche,
                    typeHashNFToken(offer.collateral)
                )
            );
    }
}
合同源代码
文件 35 的 36:Storage.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {NFToken, Ray} from "./Objects.sol";

/// @notice type definitions of data permanently stored

/// @notice Parameters affecting liquidations by dutch auctions. The current auction parameters
///         are assigned to new loans at borrow time and can't be modified during the loan life.
/// @param duration number of seconds after the auction start when the price hits 0
/// @param priceFactor multiplier of the mean tvl used as start price for the auction
struct Auction {
    uint256 duration;
    Ray priceFactor;
}

/// @notice General protocol
/// @param nbOfLoans total number of loans ever issued (active and ended)
/// @param nbOfTranches total number of interest rates tranches ever created (active and inactive)
/// @param auctionParams - sets auctions duration and initial prices
/// @param tranche interest rate of tranche of provided id, in multiplier per second
///         I.e lent * time since loan start * tranche = interests to repay
/// @param loan - of id -
/// @param minOfferCost minimum amount repaid per offer used in a loan
/// @param offerBorrowAmountLowerBound borrow amount per offer has to be strightly higher than this value
struct Protocol {
    uint256 nbOfLoans;
    uint256 nbOfTranches;
    Auction auction;
    mapping(uint256 => Ray) tranche;
    mapping(uint256 => Loan) loan;
    mapping(IERC20 => uint256) minOfferCost;
    mapping(IERC20 => uint256) offerBorrowAmountLowerBound;
}

/// @notice Issued Loan (corresponding to one collateral)
/// @param assetLent currency lent
/// @param lent total amount lent
/// @param shareLent between 0 and 1, the share of the collateral value lent
/// @param startDate timestamp of the borrowing transaction
/// @param endDate timestamp after which sale starts & repay is impossible
/// @param auction duration and price factor of the collateral auction in case of liquidation
/// @param interestPerSecond share of the amount lent added to the debt per second
/// @param borrower borrowing account
/// @param collateral NFT asset used as collateral
/// @param payment data on the payment, a non-0 payment.paid value means the loan lifecyle is over
struct Loan {
    IERC20 assetLent;
    uint256 lent;
    Ray shareLent;
    uint256 startDate;
    uint256 endDate;
    Auction auction;
    Ray interestPerSecond;
    address borrower;
    NFToken collateral;
    Payment payment;
}

/// @notice tracking of the payment state of a loan
/// @param paid amount sent on the tx closing the loan, non-zero value means loan's lifecycle is over
/// @param minInterestsToRepay minimum amount of interests that the borrower will need to repay
/// @param liquidated this loan has been closed at the liquidation stage, the collateral has been sold
/// @param borrowerClaimed borrower claimed his rights on this loan (either collateral or share of liquidation)
struct Payment {
    uint256 paid;
    uint256 minInterestsToRepay;
    bool liquidated;
    bool borrowerClaimed;
}

/// @notice storage for the ERC721 compliant supply position facet. Related NFTs represent supplier positions
/// @param name - of the NFT collection
/// @param symbol - of the NFT collection
/// @param totalSupply number of supply position ever issued - not decreased on burn
/// @param owner - of nft of id -
/// @param balance number of positions owned by -
/// @param tokenApproval address approved to transfer position of id - on behalf of its owner
/// @param operatorApproval address is approved to transfer all positions of - on his behalf
/// @param provision supply position metadata
struct SupplyPosition {
    string name;
    string symbol;
    uint256 totalSupply;
    mapping(uint256 => address) owner;
    mapping(address => uint256) balance;
    mapping(uint256 => address) tokenApproval;
    mapping(address => mapping(address => bool)) operatorApproval;
    mapping(uint256 => Provision) provision;
}

/// @notice data on a liquidity provision from a supply offer in one existing loan
/// @param amount - supplied for this provision
/// @param share - of the collateral matched by this provision
/// @param loanId identifier of the loan the liquidity went to
struct Provision {
    uint256 amount;
    Ray share;
    uint256 loanId;
}
合同源代码
文件 36 的 36:SupplyPositionFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;

import {DiamondERC721} from "./SupplyPositionLogic/DiamondERC721.sol";
import {ERC721InvalidTokenId} from "./DataStructure/ERC721Errors.sol";
import {SupplyPosition, Provision} from "./DataStructure/Storage.sol";
import {supplyPositionStorage} from "./DataStructure/Global.sol";

/// @notice NFT collection facet for transferable tradable non fungible supply positions
contract SupplyPositionFacet is DiamondERC721 {
    // constructor equivalent is in the Initializer contract

    /// @notice get metadata on provision linked to the supply position
    /// @param tokenId token identifier of the supply position
    /// @return provision metadata
    function position(uint256 tokenId) external view returns (Provision memory) {
        SupplyPosition storage sp = supplyPositionStorage();

        if (tokenId > sp.totalSupply) {
            revert ERC721InvalidTokenId();
        }

        return sp.provision[tokenId];
    }

    /// @notice total number of supply positions ever minted (counting burned ones)
    /// @return totalSupply the number
    function totalSupply() external view returns (uint256) {
        return supplyPositionStorage().totalSupply;
    }
}
设置
{
  "compilationTarget": {
    "src/annex/DeployEagle.s.sol": "KairosEagle"
  },
  "evmVersion": "london",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": [
    ":@openzeppelin/=../../node_modules/@openzeppelin/",
    ":diamond/=../../node_modules/diamond/",
    ":ds-test/=../../node_modules/ds-test/src/",
    ":forge-std/=../../node_modules/forge-std/src/"
  ]
}
ABI
[{"inputs":[{"components":[{"internalType":"address","name":"facetAddress","type":"address"},{"internalType":"enum IDiamond.FacetCutAction","name":"action","type":"uint8"},{"internalType":"bytes4[]","name":"functionSelectors","type":"bytes4[]"}],"internalType":"struct IDiamond.FacetCut[]","name":"cuts","type":"tuple[]"},{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"init","type":"address"},{"internalType":"bytes","name":"initCalldata","type":"bytes"}],"internalType":"struct DiamondArgs","name":"_args","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes4","name":"_selector","type":"bytes4"}],"name":"CannotAddFunctionToDiamondThatAlreadyExists","type":"error"},{"inputs":[{"internalType":"bytes4[]","name":"_selectors","type":"bytes4[]"}],"name":"CannotAddSelectorsToZeroAddress","type":"error"},{"inputs":[{"internalType":"bytes4","name":"_selector","type":"bytes4"}],"name":"CannotRemoveFunctionThatDoesNotExist","type":"error"},{"inputs":[{"internalType":"bytes4","name":"_selector","type":"bytes4"}],"name":"CannotRemoveImmutableFunction","type":"error"},{"inputs":[{"internalType":"bytes4","name":"_selector","type":"bytes4"}],"name":"CannotReplaceFunctionThatDoesNotExists","type":"error"},{"inputs":[{"internalType":"bytes4","name":"_selector","type":"bytes4"}],"name":"CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet","type":"error"},{"inputs":[{"internalType":"bytes4[]","name":"_selectors","type":"bytes4[]"}],"name":"CannotReplaceFunctionsFromFacetWithZeroAddress","type":"error"},{"inputs":[{"internalType":"bytes4","name":"_selector","type":"bytes4"}],"name":"CannotReplaceImmutableFunction","type":"error"},{"inputs":[{"internalType":"bytes4","name":"_functionSelector","type":"bytes4"}],"name":"FunctionNotFound","type":"error"},{"inputs":[{"internalType":"uint8","name":"_action","type":"uint8"}],"name":"IncorrectFacetCutAction","type":"error"},{"inputs":[{"internalType":"address","name":"_initializationContractAddress","type":"address"},{"internalType":"bytes","name":"_calldata","type":"bytes"}],"name":"InitializationFunctionReverted","type":"error"},{"inputs":[{"internalType":"address","name":"_contractAddress","type":"address"},{"internalType":"string","name":"_message","type":"string"}],"name":"NoBytecodeAtAddress","type":"error"},{"inputs":[{"internalType":"address","name":"_facetAddress","type":"address"}],"name":"NoSelectorsProvidedForFacetForCut","type":"error"},{"inputs":[{"internalType":"address","name":"_facetAddress","type":"address"}],"name":"RemoveFacetAddressMustBeZeroAddress","type":"error"},{"stateMutability":"payable","type":"fallback"},{"stateMutability":"payable","type":"receive"}]