Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions contracts/errors/Lending.sol
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,25 @@ error OCM_CollateralMismatch(
*/
error OCM_LenderIsBorrower();

// ============================ ORIGINATION CONTROLLER INTEREST RATE SWAP ===============================
/// @notice All errors prefixed with OCIRS_, to separate from other contracts in the protocol.

/**
* @notice Zero address passed in where not allowed.
*
* @param addressType The name of the parameter for which a zero address was provided.
*/
error OCIRS_ZeroAddress(string addressType);

/**
* @notice The loan terms payable currency and vaulted currency are not whitelisted as a valid
* currency pair for interest rate swaps.
*
* @param payableCurrency The currency of the loan terms.
* @param vaultedCurrency The currency of the vaulted collateral.
*/
error OCIRS_InvalidPair(address payableCurrency, address vaultedCurrency);

// ================================== CROSS CURRENCY ROLLOVER ====================================
/// @notice All errors prefixed with CCR_, to separate from other contracts in the protocol.

Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IOriginationController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface IOriginationController is IOriginationControllerBase {
// ============= Loan Origination =============

function initializeLoan(
LoanLibrary.LoanTerms calldata loanTerms,
LoanLibrary.LoanTerms calldata loanTerms,
BorrowerData calldata borrowerData,
address lender,
Signature calldata sig,
Expand Down
39 changes: 39 additions & 0 deletions contracts/interfaces/IOriginationControllerInterestRateSwap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import "./IOriginationControllerBase.sol";

import "../libraries/LoanLibrary.sol";

interface IOriginationControllerInterestRateSwap is IOriginationControllerBase {

// ============= Data Types =============

struct SwapData {
address vaultedCurrency;
uint256 payableToVaultedCurrencyRatio;
}

// ============= Loan Origination =============

function initializeSwap(
LoanLibrary.LoanTerms calldata loanTerms,
SwapData calldata swapData,
address borrower,
address lender,
Signature calldata sig,
SigProperties calldata sigProperties
) external returns (uint256 loanId, uint256 bundleId);

// ============= Signature Verification =============

function recoverInterestRateSwapSignature(
LoanLibrary.LoanTerms calldata loanTerms,
Signature calldata sig,
SigProperties calldata sigProperties,
address vaultedCurrency,
Side side,
address signingCounterparty
) external view returns (bytes32 sighash, address signer);
}
70 changes: 41 additions & 29 deletions contracts/libraries/OriginationLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

pragma solidity 0.8.18;

import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";

import "../libraries/LoanLibrary.sol";

import "../interfaces/IOriginationController.sol";
Expand All @@ -13,7 +10,9 @@ import "../interfaces/IOriginationController.sol";
* @title OriginationLibrary
* @author Non-Fungible Technologies, Inc.
*
* Library for loan origination functions.
* This library is a collection of shared logic used across various origination controller contracts.
* It includes constants for EIP712 type hashes, the functions for encoding these type hashes, and
* various data structures shared by the origination controller contracts.
*/
library OriginationLibrary {
// ======================================= STRUCTS ================================================
Expand Down Expand Up @@ -42,21 +41,26 @@ library OriginationLibrary {
}

// ======================================= CONSTANTS ==============================================
// solhint-disable max-line-length

/// @notice EIP712 type hash for bundle-based signatures.
bytes32 public constant _TOKEN_ID_TYPEHASH =
keccak256(
// solhint-disable-next-line max-line-length
"LoanTerms(uint32 interestRate,uint64 durationSecs,address collateralAddress,uint96 deadline,address payableCurrency,uint256 principal,uint256 collateralId,bytes32 affiliateCode,SigProperties sigProperties,uint8 side,address signingCounterparty)SigProperties(uint160 nonce,uint96 maxUses)"
);

/// @notice EIP712 type hash for item-based signatures.
bytes32 public constant _ITEMS_TYPEHASH =
keccak256(
// solhint-disable max-line-length
"LoanTermsWithItems(uint32 interestRate,uint64 durationSecs,address collateralAddress,uint96 deadline,address payableCurrency,uint256 principal,bytes32 affiliateCode,Predicate[] items,SigProperties sigProperties,uint8 side,address signingCounterparty)Predicate(bytes data,address verifier)SigProperties(uint160 nonce,uint96 maxUses)"
);

/// @notice EIP712 type hash for interest rate swap signatures.
bytes32 public constant _INTEREST_RATE_SWAP_TYPEHASH =
keccak256(
"LoanTermsWithCurrencyPair(uint32 interestRate,uint64 durationSecs,address vaultedCurrency,address collateralAddress,uint96 deadline,address payableCurrency,uint256 principal,uint256 collateralId,bytes32 affiliateCode,SigProperties sigProperties,uint8 side,address signingCounterparty)SigProperties(uint160 nonce,uint96 maxUses)"
);

/// @notice EIP712 type hash for Predicate.
bytes32 public constant _PREDICATE_TYPEHASH =
keccak256(
Expand Down Expand Up @@ -192,33 +196,41 @@ library OriginationLibrary {
);
}

// ==================================== PERMISSION MANAGEMENT =====================================

/**
* @notice Reports whether the signer matches the target or is approved by the target.
* @notice Hashes a loan with interest rate swap for inclusion in the EIP712 signature.
*
* @param target The grantor of permission - should be a smart contract.
* @param sig A struct containing the signature data (for checking EIP-1271).
* @param sighash The hash of the signature payload (used for EIP-1271 check).
* @param terms The loan terms.
* @param sigProperties The signature properties.
* @param vaultedCurrency The currency to be vaulted.
* @param side The side of the signature.
* @param signingCounterparty The address of the signing counterparty.
*
* @return bool Whether the signer is either the grantor themselves, or approved.
* @return loanHash The hash of the loan.
*/
function isApprovedForContract(
address target,
IOriginationController.Signature memory sig,
bytes32 sighash
) public view returns (bool) {
bytes memory signature = abi.encodePacked(sig.r, sig.s, sig.v);

// Append extra data if it exists
if (sig.extraData.length > 0) {
signature = bytes.concat(signature, sig.extraData);
}

// Convert sig struct to bytes
(bool success, bytes memory result) = target.staticcall(
abi.encodeWithSelector(IERC1271.isValidSignature.selector, sighash, signature)
function encodeLoanWithInterestRateSwap(
LoanLibrary.LoanTerms calldata terms,
IOriginationController.SigProperties calldata sigProperties,
address vaultedCurrency,
uint8 side,
address signingCounterparty
) public pure returns (bytes32 loanHash) {
loanHash = keccak256(
abi.encode(
_INTEREST_RATE_SWAP_TYPEHASH,
terms.interestRate,
terms.durationSecs,
vaultedCurrency,
terms.collateralAddress,
terms.deadline,
terms.payableCurrency,
terms.principal,
terms.collateralId,
terms.affiliateCode,
encodeSigProperties(sigProperties),
uint8(side),
signingCounterparty
)
);
return (success && result.length == 32 && abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}
}
}
55 changes: 8 additions & 47 deletions contracts/origination/OriginationController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,12 @@ import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "./OriginationControllerBase.sol";

import "../interfaces/IOriginationController.sol";

import "../libraries/FeeLookups.sol";
import "../interfaces/IFeeController.sol";
import "../interfaces/IExpressBorrow.sol";

import { OC_InvalidState } from "../errors/Lending.sol";

import {
OC_ApprovedOwnLoan,
OC_InvalidSignature,
OC_CallerNotParticipant,
OC_SideMismatch,
OC_RolloverCurrencyMismatch,
OC_RolloverCollateralMismatch,
OC_ZeroAddress
Expand All @@ -43,7 +39,12 @@ import {
* does not move from escrow in LoanCore. Only the payable currency is transferred
* where applicable.
*/
contract OriginationController is IOriginationController, OriginationControllerBase, FeeLookups, AccessControlEnumerable, ReentrancyGuard {
contract OriginationController is
IOriginationController,
OriginationControllerBase,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove import "../libraries/FeeLookups.sol"; from line 15.

AccessControlEnumerable,
ReentrancyGuard
{
using SafeERC20 for IERC20;

// ============================================ STATE ==============================================
Expand Down Expand Up @@ -316,44 +317,4 @@ contract OriginationController is IOriginationController, OriginationControllerB
newTerms.collateralId
);
}

/**
* @dev Ensure that one counterparty has signed the loan terms, and the other
* has initiated the transaction.
*
* @param signingCounterparty The address of the counterparty who signed the terms.
* @param callingCounterparty The address on the other side of the loan as the signingCounterparty.
* @param caller The address initiating the transaction.
* @param signer The address recovered from the loan terms signature.
* @param sig A struct containing the signature data (for checking EIP-1271).
* @param sighash The hash of the signature payload (used for EIP-1271 check).
*/
// solhint-disable-next-line code-complexity
function _validateCounterparties(
address signingCounterparty,
address callingCounterparty,
address caller,
address signer,
Signature calldata sig,
bytes32 sighash
) internal view {
// Make sure the signer recovered from the loan terms is not the caller,
// and even if the caller is approved, the caller is not the signing counterparty
if (caller == signer || caller == signingCounterparty) revert OC_ApprovedOwnLoan(caller);

// Check that caller can actually call this function - neededSide assignment
// defaults to BORROW if the signature is not approved by the borrower, but it could
// also not be a participant
if (!isSelfOrApproved(callingCounterparty, caller)) {
revert OC_CallerNotParticipant(msg.sender);
}

// Check signature validity
if (!isSelfOrApproved(signingCounterparty, signer) && !OriginationLibrary.isApprovedForContract(signingCounterparty, sig, sighash)) {
revert OC_InvalidSignature(signingCounterparty, signer);
}

// Revert if the signer is the calling counterparty
if (signer == callingCounterparty) revert OC_SideMismatch(signer);
}
}
Loading