Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
61 changes: 61 additions & 0 deletions contracts/errors/Lending.sol
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,67 @@ error OCM_CollateralMismatch(
*/
error OCM_LenderIsBorrower();

// =============================== ORIGINATION CONTROLLER STIRFRY ================================
/// @notice All errors prefixed with OCS_, 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 OCS_ZeroAddress(string addressType);

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

/**
* @notice The loan terms principal multiplied by the payableToVaultedCurrencyRatio does not
* equal the vaulted collateral amount.
*
* @param principal The principal amount in the loan terms.
* @param payableToVaultedCurrencyRatio The 1 to 1 ratio of the vaulted collateral amount to the
* loan terms payable currency amount.
* @param lenderVaultedCurrencyAmount The amount of vaulted collateral.
*/
error OCS_InvalidPrincipalAmounts(
uint256 principal,
uint256 payableToVaultedCurrencyRatio,
uint256 lenderVaultedCurrencyAmount
);

/**
* @notice The vaulted currency amount specified is not equivalent to what is actually vaulted.
*
* @param actualVaultedCollateralAmount The actual balance of collateral ERC20 in the vault.
* @param proviededVaultedCurrencyAmount The provided input for loan origination.
Copy link
Contributor

Choose a reason for hiding this comment

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

proviededVaultedCurrencyAmount typo in provided.

*/
error OCS_InvalidVaultAmount(
uint256 actualVaultedCollateralAmount,
uint256 proviededVaultedCurrencyAmount
Copy link
Contributor

Choose a reason for hiding this comment

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

proviededVaultedCurrencyAmount typo in provided.

);

/**
* @notice The total interest due over the duration of the loan terms multiplied by the
* payableToVaultedCurrencyRatio does not equal the vaulted fixed interest amount
* provided by the borrower.
*
* @param totalInterest The total interest due.
* @param payableToVaultedCurrencyRatio The 1 to 1 ratio of the vaulted collateral amount to the
* loan terms payable currency amount.
* @param borrowerVaultedCurrencyAmount The fixed interest amount provided by the borrower.
*/
error OCS_InvalidInterestAmounts(
uint256 totalInterest,
uint256 payableToVaultedCurrencyRatio,
uint256 borrowerVaultedCurrencyAmount
);

// ================================== 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
31 changes: 31 additions & 0 deletions contracts/interfaces/IOriginationControllerSTIRFRY.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import "./IOriginationControllerBase.sol";

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

interface IOriginationControllerSTIRFRY is IOriginationControllerBase {
Copy link
Contributor

Choose a reason for hiding this comment

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

Will only leave this once in code, but see overall comment about naming


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

struct StirfryData {
address vaultedCurrency;
uint256 lenderVaultedCurrencyAmount;
uint256 borrowerVaultedCurrencyAmount;
uint256 payableToVaultedCurrencyRatio;
Copy link
Contributor

Choose a reason for hiding this comment

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

Re: ratio here? Does this only imply a ratio in terms of the number of decimals each token uses, or does it also imply some "price ratio"? If it's decimals, should it be encoded in allowedPairs instead of being provided by a counterparty?

If it's provided by a counterparty, then it seems like the caller can put in whatever they want. If I were swapping variable-to-fixed, then I'd just make it 0 (it's also not validated anywhere, so I could). Then, line 200 would mean that stirfryData.borrowerVaultedCurrencyAmount would have to equal 0. That's great for me, I just opened a loan without having to actually vault anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, the payableToVaultedCurrencyRatio is the decimals ratio. This ratio is currently being captured by the allowedPairs hash. See the hash setter function as well as the validation function.

}

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

function initializeStirfryLoan(
LoanLibrary.LoanTerms calldata loanTerms,
StirfryData calldata stirfryData,
address borrower,
address lender,
Signature calldata sig,
SigProperties calldata sigProperties,
LoanLibrary.Predicate[] calldata itemPredicates
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't want to use predicates at all here; it seems like that only confuses things? All the balances we need for the swap are already being checked by this contract.

) external returns (uint256 loanId);
}
53 changes: 8 additions & 45 deletions contracts/origination/OriginationController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@ import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "./OriginationControllerBase.sol";

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

import "../libraries/FeeLookups.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 +41,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 +319,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);
}
}
65 changes: 51 additions & 14 deletions contracts/origination/OriginationControllerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,30 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

import "./OriginationCalculator.sol";

import "../interfaces/IOriginationController.sol";
import "../interfaces/IOriginationHelpers.sol";
import "../interfaces/ILoanCore.sol";
import "../interfaces/IFeeController.sol";
import "../interfaces/IExpressBorrow.sol";

import "../libraries/OriginationLibrary.sol";
import "../libraries/Constants.sol";

import "../verifiers/ArcadeItemsVerifier.sol";

import { OC_ZeroAddress, OC_SelfApprove } from "../errors/Lending.sol";
import {
OC_ZeroAddress,
OC_SelfApprove,
OC_SideMismatch,
OC_ApprovedOwnLoan,
OC_CallerNotParticipant,
OC_InvalidSignature
} from "../errors/Lending.sol";

/**
* @title OriginationControllerBase
* @author Non-Fungible Technologies, Inc.
*
* The Origination Controller Base contract provides common functionality for all
* origination controllers, including signature verification and access control.
* It establishes access roles and integrates permission management along with
* signature verification functions.
*
* origination controllers, including signature verification, shared reference
* contracts, approved third party originators, and rollover calculation helpers.
*/
abstract contract OriginationControllerBase is IOriginationControllerBase, EIP712, OriginationCalculator {
// ============================================ STATE ==============================================
// ============================================ STATE ===============================================
// =============== Contract References ===============

IOriginationHelpers public immutable originationHelpers;
Expand Down Expand Up @@ -170,7 +169,7 @@ abstract contract OriginationControllerBase is IOriginationControllerBase, EIP71
signer = ECDSA.recover(sighash, sig.v, sig.r, sig.s);
}

// ============================================ HELPER ==============================================
// ============================================ HELPERS =============================================

/**
* @notice Determine the sighash and external signer given the loan terms, signature, nonce,
Expand Down Expand Up @@ -214,6 +213,44 @@ abstract contract OriginationControllerBase is IOriginationControllerBase, EIP71
);
}
}
}

/**
* @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(
Copy link
Contributor

Choose a reason for hiding this comment

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

This function can stay in OriginationController and OriginationControllerSTIRFRY because those are the only 2 contracts using it. CrossCurrencyRollover which also inherits OriginationControllerBase, does not use _validateCounterparties. And other potential new contracts may not use it either.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason for putting it in the OCBase contract is so there is not duplicate code across two different OriginationControllers. Additionally, the OCBase contract is home to isSelfOrApproved() which gets called twice in _validateCounterparties() so it seemed to make more sense to have it located in the OCBase contract. Other OriginationControllers that inherit the OCBase that don't need _validateCounterparties() should not be affected since it is an internal function.

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