GondiRefinancingAdapter

Description:

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

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
"
    },
    "contracts/refinancing/refinancingAdapters/gondi/IGondi.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;

/// @title Multi Source Loan Interface
/// @author Florida St
/// @notice A multi source loan is one with multiple tranches.
interface IGondi {
    /// @notice Borrowers receive offers that are then validated.
    /// @dev Setting the nftCollateralTokenId to 0 triggers validation through `validators`.
    /// @param offerId Offer ID. Used for canceling/setting as executed.
    /// @param lender Lender of the offer.
    /// @param fee Origination fee.
    /// @param capacity Capacity of the offer.
    /// @param nftCollateralAddress Address of the NFT collateral.
    /// @param nftCollateralTokenId NFT collateral token ID.
    /// @param principalAddress Address of the principal.
    /// @param principalAmount Principal amount of the loan.
    /// @param aprBps APR in BPS.
    /// @param expirationTime Expiration time of the offer.
    /// @param duration Duration of the loan in seconds.
    /// @param maxSeniorRepayment Max amount of senior capital ahead (principal + interest).
    /// @param validators Arbitrary contract to validate offers implementing `IBaseOfferValidator`.
    struct LoanOffer {
        uint256 offerId;
        address lender;
        uint256 fee;
        uint256 capacity;
        address nftCollateralAddress;
        uint256 nftCollateralTokenId;
        address principalAddress;
        uint256 principalAmount;
        uint256 aprBps;
        uint256 expirationTime;
        uint256 duration;
        uint256 maxSeniorRepayment;
        OfferValidator[] validators;
    }

    /// @notice Arbitrary contract to validate offers implementing `IBaseOfferValidator`.
    /// @param validator Address of the validator contract.
    /// @param arguments Arguments to pass to the validator.
    struct OfferValidator {
        address validator;
        bytes arguments;
    }

    /// @notice Offer + how much will be filled (always <= principalAmount).
    /// @param offer Offer.
    /// @param amount Amount to be filled.
    struct OfferExecution {
        LoanOffer offer;
        uint256 amount;
        bytes lenderOfferSignature;
    }

    /// @notice Offer + necessary fields to execute a specific loan. This has a separate expirationTime to avoid
    /// someone holding an offer and executing much later, without the borrower's awareness.
    /// @dev It's advised that borrowers only set an expirationTime close to the actual time they will execute the loan
    ///      to avoid replays.
    /// @param offerExecution List of offers to be filled and amount for each.
    /// @param tokenId NFT collateral token ID.
    /// @param amount The amount the borrower is willing to take (must be <= _loanOffer principalAmount)
    /// @param expirationTime Expiration time of the signed offer by the borrower.
    /// @param callbackData Data to pass to the callback.
    struct ExecutionData {
        OfferExecution[] offerExecution;
        uint256 tokenId;
        uint256 duration;
        uint256 expirationTime;
        address principalReceiver;
        bytes callbackData;
    }

    /// @param executionData Execution data.
    /// @param borrower Address that owns the NFT and will take over the loan.
    /// @param borrowerOfferSignature Signature of the offer (signed by borrower).
    /// @param callbackData Whether to call the afterPrincipalTransfer callback
    struct LoanExecutionData {
        ExecutionData executionData;
        address borrower;
        bytes borrowerOfferSignature;
    }

    /// @param loanId Loan ID.
    /// @param callbackData Whether to call the afterNFTTransfer callback
    /// @param shouldDelegate Whether to delegate ownership of the NFT (avoid seaport flags).
    struct SignableRepaymentData {
        uint256 loanId;
        bytes callbackData;
        bool shouldDelegate;
    }

    /// @param loan Loan.
    /// @param borrowerLoanSignature Signature of the loan (signed by borrower).
    struct LoanRepaymentData {
        SignableRepaymentData data;
        Loan loan;
        bytes borrowerSignature;
    }

    /// @notice Tranches have different seniority levels.
    /// @param loanId Loan ID.
    /// @param floor Amount of principal more senior to this tranche.
    /// @param principalAmount Total principal in this tranche.
    /// @param lender Lender for this given tranche.
    /// @param accruedInterest Accrued Interest.
    /// @param startTime Start Time. Either the time at which the loan initiated / was refinanced.
    /// @param aprBps APR in basis points.
    struct Tranche {
        uint256 loanId;
        uint256 floor;
        uint256 principalAmount;
        address lender;
        uint256 accruedInterest;
        uint256 startTime;
        uint256 aprBps;
    }

    /// @dev Principal Amount is equal to the sum of all tranches principalAmount.
    /// We keep it for caching purposes. Since we are not saving this on chain but the hash,
    /// it does not have a huge impact on gas.
    /// @param borrower Borrower.
    /// @param nftCollateralTokenId NFT Collateral Token ID.
    /// @param nftCollateralAddress NFT Collateral Address.
    /// @param principalAddress Principal Address.
    /// @param principalAmount Principal Amount.
    /// @param startTime Start Time.
    /// @param duration Duration.
    /// @param tranche Tranches.
    /// @param protocolFee Protocol Fee.
    struct Loan {
        address borrower;
        uint256 nftCollateralTokenId;
        address nftCollateralAddress;
        address principalAddress;
        uint256 principalAmount;
        uint256 startTime;
        uint256 duration;
        Tranche[] tranche;
        uint256 protocolFee;
    }

    /// @notice Recipient address and fraction of gains charged by the protocol.
    struct ProtocolFee {
        address recipient;
        uint256 fraction;
    }

    function updateProtocolFee(ProtocolFee calldata _newProtocolFee) external;
    function getProtocolFee() external view returns (ProtocolFee memory);
    function owner() external view returns (address);
    function setProtocolFee() external;

    // solhint-disable-next-line func-name-mixedcase
    function VERSION() external view returns (bytes calldata);
    function name() external view returns (string calldata);

    /// @notice Call by the borrower when emiting a new loan.
    /// @param _loanExecutionData Loan execution data.
    /// @return loanId Loan ID.
    /// @return loan Loan.
    function emitLoan(LoanExecutionData calldata _loanExecutionData) external returns (uint256, Loan memory);

    /// @notice Repay loan. Interest is calculated pro-rata based on time. Lender is defined by nft ownership.
    /// @param _repaymentData Repayment data.
    function repayLoan(LoanRepaymentData calldata _repaymentData) external;

    event LoanEmitted(uint256 loanId, uint256[] offerId, Loan loan, uint256 fee);

    error CancelledOrExecutedOfferError(address _lender, uint256 _offerId);
    error ExpiredOfferError(uint256 _expirationTime);
    error LowOfferIdError(address _lender, uint256 _newMinOfferId, uint256 _minOfferId);
    error LowRenegotiationOfferIdError(address _lender, uint256 _newMinRenegotiationOfferId, uint256 _minOfferId);
    error ZeroInterestError();
    error InvalidSignatureError();
    error CurrencyNotWhitelistedError();
    error CollectionNotWhitelistedError();
    error MaxCapacityExceededError();
    error InvalidLoanError(uint256 _loanId);
    error NotStrictlyImprovedError();
    error InvalidAmountError(uint256 _amount, uint256 _principalAmount);

    error InvalidParametersError();
    error MismatchError();
    error InvalidCollateralIdError();
    error InvalidMethodError();
    error InvalidAddressesError();
    error InvalidCallerError();
    error InvalidTrancheError();
    error InvalidRenegotiationOfferError();
    error TooManyTranchesError();
    error LoanExpiredError();
    error NFTNotReturnedError();
    error TrancheCannotBeRefinancedError(uint256 minTimestamp);
    error LoanLockedError();

    error AddressAlreadyAddedError(address _address);
    error AddressNotAddedError(address _address);

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    error InvalidCallbackError();
    error MulticallFailed();
    error InvalidOwnerError();
    error AddressZeroError();

    error LiquidatorOnlyError(address _liquidator);

    error LoanNotDueError(uint256 _expirationTime);

    error InvalidDurationError();
    error StringsInsufficientHexLength(uint256 value, uint256 length);
}
"
    },
    "contracts/refinancing/refinancingAdapters/GondiRefinancingAdapter.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.19;

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

import {IGondi} from "./gondi/IGondi.sol";

/**
 * @title GondiRefinancingAdapter
 * @author NFTfi
 * @dev This contract is an implementation of the IRefinancingAdapter for the Gondi platform.
 * It handles operations related to refinancing Gondi loans such as transferring the borrower role,
 * paying off loans, and retrieving loan and collateral details.
 */
contract GondiRefinancingAdapter is IRefinancingAdapter {
    /// @notice Precision used for calculating interests.
    uint256 internal constant PRECISION = 10000;
    uint256 private constant SECONDS_PER_YEAR = 31536000;
    uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;

    error transferBorrowerRoleFailed();
    error LoanIdMismatch();

    /**
     * @dev Gets the address of the borrower for a specific Gondi loan.
     * @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
     * This data must be retrieved from events as Gondi does not provide a direct query method.
     * @return address of the borrower from the encoded Lien struct.
     */
    function getBorrowerAddress(address, uint256, bytes calldata _extraData) external pure override returns (address) {
        IGondi.LoanRepaymentData memory loanRepaymentData = _decodeExtraData(_extraData);
        return loanRepaymentData.loan.borrower;
    }

    /**
     * @dev Handles the borrower role transfer for Gondi loans.
     * @notice For Gondi, no borrower role transfer is possible, unused function
     * @return Always returns true as no additional action is required.
     */
    function transferBorrowerRole(address, uint256, bytes calldata) external pure override returns (bool) {
        return true;
    }

    /**
     * @dev Pays off a Gondi loan as part of the refinancing process.
     * @param _loanContract The address of the Gondi contract.
     * @param _payBackAmount The amount of WETH tokens used to pay back the Gondi loan.
     * @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
     * @return A boolean value indicating whether the operation was successful.
     * @notice This function calls Gondi's repay function with the loan repayment details.
     */
    function payOffRefinancable(
        address _loanContract,
        uint256 _loanId,
        address _payBackToken,
        uint256 _payBackAmount,
        bytes calldata _extraData
    ) external override returns (bool) {
        IGondi.LoanRepaymentData memory loanRepaymentData = _decodeExtraData(_extraData);
        if (loanRepaymentData.loan.tranche[0].loanId != _loanId) {
            revert LoanIdMismatch();
        }
        IERC20(_payBackToken).transfer(loanRepaymentData.loan.borrower, _payBackAmount);
        IGondi(_loanContract).repayLoan(loanRepaymentData);
        return true;
    }

    /**
     * @dev Gets the NFT collateral information for a specific Gondi loan.
     * @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
     * @return nftCollateralContract The address of the NFT collection contract.
     * @return nftCollateralId The token ID of the NFT used as collateral.
     */
    function getCollateral(
        address,
        uint256,
        bytes calldata _extraData
    ) external pure override returns (address, uint256) {
        IGondi.Loan memory loan = _decodeExtraData(_extraData).loan;
        return (loan.nftCollateralAddress, loan.nftCollateralTokenId);
    }

    /**
     * @dev Gets the payoff details for a specific Gondi loan.
     * @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
     * @return loanERC20Denomination The address of the payoff token (always WETH for Gondi).
     * @return maximumRepaymentAmount The total amount required to pay off the loan, calculated based
     * on the principal, interest rate, and time elapsed since loan creation.
     * @notice This function calculates the current debt including accrued interest at the moment of calling.
     * We are using the exact logic from Gondi's computeCurrentDebt function
     */
    function getPayoffDetails(
        address,
        uint256,
        bytes calldata _extraData
    ) external view override returns (address, uint256) {
        IGondi.Loan memory loan = _decodeExtraData(_extraData).loan;
        address payoffToken = loan.principalAddress;
        uint256 payOffAmount = _calculateRepayment(loan);
        return (payoffToken, payOffAmount);
    }

    /**
     * @dev Helper function to decode the Lien struct from the encoded extraData.
     * @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
     * @return loanRepaymentData The decoded Lien struct containing all loan details.
     */
    function _decodeExtraData(
        bytes calldata _extraData
    ) internal pure returns (IGondi.LoanRepaymentData memory loanRepaymentData) {
        loanRepaymentData = abi.decode(_extraData, (IGondi.LoanRepaymentData));
    }

    function _calculateRepayment(IGondi.Loan memory loan) private view returns (uint256) {
        uint256 totalRepayment = 0;

        uint256 totalTranches = loan.tranche.length;
        for (uint256 i; i < totalTranches; ) {
            IGondi.Tranche memory tranche = loan.tranche[i];
            uint256 newInterest = _getInterest(
                tranche.principalAmount,
                tranche.aprBps,
                block.timestamp - tranche.startTime
            );
            uint256 repayment = tranche.principalAmount + tranche.accruedInterest + newInterest;
            unchecked {
                totalRepayment += repayment;
            }
            unchecked {
                ++i;
            }
        }
        return totalRepayment;
    }

    function _getInterest(uint256 _amount, uint256 _aprBps, uint256 _duration) private pure returns (uint256) {
        uint256 interest = mulDivUp(_amount, _aprBps * _duration, PRECISION * SECONDS_PER_YEAR);
        return interest;
    }

    function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // If x * y modulo the denominator is strictly greater than 0,
            // 1 is added to round up the division of x * y by the denominator.
            z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
        }
    }
}
"
    },
    "contracts/refinancing/refinancingAdapters/IRefinancingAdapter.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.19;

/**
 * @title IRefinancingAdapter
 * @author NFTfi
 *
 * @dev This is the interface for Refinancing Adapters. It provides several methods for managing and retrieving
 * information about contracts that are eligible for refinancing.
 *
 * Adapters should implement this interface
 */
interface IRefinancingAdapter {
    /**
     * @dev Returns the borrower's address for a specific refinancable
     *
     * @param _refinanceableContract Address of the contract containing the refinanceable
     * @param _refinancableIdentifier Unique identifier for the refinanceable.
     *
     * @return Address of the borrower.
     */
    function getBorrowerAddress(
        address _refinanceableContract,
        uint256 _refinancableIdentifier,
        bytes memory _extraData
    ) external returns (address);

    /**
     * @dev Transfers the role of borrower to refinancing contract for a specific refinanceable.
     *
     * @param _refinanceableContract Address of the contract containing the refinanceable
     * @param _refinancableIdentifier Unique identifier for the loan.
     *
     * @return True if the operation was successful.
     */
    function transferBorrowerRole(
        address _refinanceableContract,
        uint256 _refinancableIdentifier,
        bytes memory _extraData
    ) external returns (bool);

    /**
     * @dev Pays off a refinanceable with a specified amount of a specified token.
     *
     * @param _refinanceableContract Address of the contract containing the refinanceable
     * @param _refinancableIdentifier Unique identifier for the refinanceable.
     * @param _payBackToken Token used to pay back the refinanceable.
     * @param _payBackAmount Amount of tokens used to pay back the refinanceable.
     *
     * @return True if the operation was successful.
     */
    function payOffRefinancable(
        address _refinanceableContract,
        uint256 _refinancableIdentifier,
        address _payBackToken,
        uint256 _payBackAmount,
        bytes memory _extraData
    ) external returns (bool);

    /**
     * @dev Returns the collateral information for a specific refinancable.
     *
     * @param _refinanceableContract Address of the contract containing the refinanceable
     * @param _refinancableIdentifier Unique identifier for the refinanceable.
     *
     * @return The address of the collateral token and the amount of collateral.
     */
    function getCollateral(
        address _refinanceableContract,
        uint256 _refinancableIdentifier,
        bytes memory _extraData
    ) external view returns (address, uint256);

    /**
     * @dev Returns the payoff details for a specific refinancable.
     *
     * @param _refinanceableContract Address of the contract containing the refinanceable
     * @param _refinancableIdentifier Unique identifier for the loan.
     *
     * @return The address of the payoff token and the required payoff amount.
     */
    function getPayoffDetails(
        address _refinanceableContract,
        uint256 _refinancableIdentifier,
        bytes memory _extraData
    ) external view returns (address, uint256);
}
"
    }
  },
  "settings": {
    "metadata": {
      "bytecodeHash": "none",
      "useLiteralContent": true
    },
    "optimizer": {
      "enabled": true,
      "runs": 900
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC20, Multisig, Upgradeable, Multi-Signature, Factory|addr:0x4d72cfb5b8f642dd86cd48dd8830f74095a52b4c|verified:true|block:23496526|tx:0xd7e400b81025012e694c6132c91f23d7525024fdd918fe88b4433567df68d850|first_check:1759496590

Submitted on: 2025-10-03 15:03:11

Comments

Log in to comment.

No comments yet.